An additional Spigot library aimed to be the dependency that saves developers time from pointless tasks and brings a bunch of useful utilities for Bukkit/Spigot/Paper developers to use!
This library was heavily inspired by RedLib, Spiglin, SpigotUtils, but it aims to provide more simple features and easier usage! Some of the great features of SpigotAdditions include:
public static @Listener void onPlayerJoin(PlayerJoinEvent event) {
event.getPlayer().sendMessage("Hello!");
}
No need to register the listener or put it in a class that implements Listener, you can put the annotation above any static method that's parameter is an event, and SpigotAdditions will automatically do everything else for you! This has existed since the very first release, but due to the various improvements and bugfixes, it is recommended to use versions above 1.2!
public class TestCommand {
public static void createCommand() {
CommandBuilder.create("test")
.setAliases("t", "test")
.setDescription("testCommand")
.setPermission("spigotadditions.test")
.setPlayerOnly(true)
.setUsage("/test")
.setTabCompletion((sender, args) -> {
//add tab completion logic here
})
.executes((sender, args) -> {
//add command logic here
//args parameter is a custom CommandArguments class
//CommandArguments allow you to automatically cast an arg to what you need
//for example:
if (args.get(0).isPlayer()) Player player = args.get(0).toPlayer();
//and so on, there are many things to reduce boilerplate and increase productivity
})
.buildAndRegister();
}
Again, no need to put it in the plugin.yml the builder manages itself, there are also more methods in the builder that you can check out! All you have to do is implement your logic, and than just make sure that the method where you put the command registration is called upon startup!
private EnchantmentWrapper(String key, String name, int maxLevel, boolean treasure, boolean cursed, EnchantmentTarget target, Enchantment... conflicts)
Above is the constructor that is used to create an EnchantmentWrapper instance.
private EnchantmentWrapper(Plugin plugin, String key, String name, int maxLevel, boolean treasure, boolean cursed, EnchantmentTarget target, Enchantment... conflicts)
The above can be used as well to register the enchantment to the plugin instead of a Minecraft enchantment.
Enchantment TELEKINESIS = EnchantmentWrapper.createEnchantment("telekinesis", "Telekinesis", 1, false, false, EnchantmentTarget.TOOL);
And this above is it! Yup, you can create a custom enchantment with a single line of code! This, combined with the Listener API mentioned above can be used to add functionality to your custom enchantments!
ItemStack item = new ItemStackBuilder(Material.BOOK)
.name("Test")
.lore("Test")
.amount(1)
.modelData(1)
.enchant(Enchantment.DURABILITY, 1);
There is a lot more to the ItemStackBuilder class, like adding persistent data tags with a single method, adding attributes, clearing all enchants, etc.
InventoryGUI iGui = new InventoryGUI("Test", 54);
iGui.fillEmptySlots(new ItemStackBuilder(Material.GRAY_STAINED_GLASS_PANE).name(""));
iGui.addButtons(GUIButton.create(new ItemStackBuilder(Material.BOOK).name(ChatColor.RED + "Test"), event -> {
event.getWhoClicked().sendMessage("Test");
}));
Inventory gui = iGui.getInventory();
This automatically makes all slots in the inventory unclickable except for the buttons and the ones that you say should be open in your code! It does all the GUI setting up for you, so you don't have to bother with that! There is A LOT more in the InventoryGUI class that you can use, you can discover all the amazing features by using the library!
PaginatedGUI gui = new PaginatedGUI();
gui.addPage(iGui);
gui.nextPage(player);
gui.previousPage(player);
gui.getCurrentPage();
gui.getPages().forEach(page -> {
page.addButtons(new SetPageButton(item, gui, SetPageButtonType.NEXT), new SetPageButton(item, gui, SetPageButtonType.PREVIOUS));
}
Introduced in version 0.2, PaginatedGUIs can also be created, and they are as simple to create and use as one can imagine, 0.2 also introduced SetPageButtons, that are GUIButtons that willl automatically do all the PaginatedGUI page setting for you!
Since 1.2, there is also MenuGUI, similar to InventoryGUI, but it allows creation of GUIs that aren't chest inventories, but of any type.
ItemStack customItem = CustomItem.from(item, "plugin_name:test_custom_item", PLUGININSTANCE);
This will add data to the item (ItemStack) that is passed as the first parameter, effectively turning it into a custom item! To further increase it's capabilities, you can use the ItemStackBuilder and the Listener API. You can always check if an item is a custom item using:
CustomItem.isCustomItem(item, PLUGININSTANCE);
There is also a utility class for easily getting player heads as items!
ItemStack head = new PlayerHeadItem(player).get();
SQLConnection sql = new SQLConnection(host, port, database, name, pass);
sql.execute("CREATE TABLE IF NOT EXISTS tablename");
sql.forEach("GET * FROM db", result -> System.out.println(result));
sql.getResults("GET * FROM db");
sql.getConnection();
sql.close();
And a lot more! Most of the queries are ran async, so you don't have to worry about that!
MongoConnection mongo = new MongoConnection("mongo-db-url", "db-name");
mongo.createCollection("collection-name");
String playerName = mongo.getString("collection-name", "playerName");
int playerKills = mongo.getInt("collection-name", "playerKills");
mongo.dropCollection("collection-name");
And a bunch of other useful Mongo-interaction methods! The above example works for any type of document, but you can also use your own POJOs as data archetypes for saving data on the Mongo database! An example is below!
MongoConnectionPOJO<Profile> mongoProfiles = new MongoConnectionPOJO("mongo-db-url", "db-name", "collection-name", Profile.class);
Profile profile = new Profile(player.getUniqueId());
mongoProfiles.insertOne(profile);
List<Profile> profiles = new ArrayList<>();
mongoProfiles.into(profiles); //will insert all profile data from the database into the profiles variable
List<Profile> profiles = mongoProfiles.getAll(); //you can also define the limit and skip if needed
Updater updater = new Updater(PLUGININSTANCE, RESOURCEID);
String latestVersion = updater.getLatest();
updater.download(); //automatically downloads the newest version!
String version = BukkitUtils.getServerVersion();
Class<?> clazz1 = BukkitUtils.getNMSClass("MinecraftServer");
Class<?> clazz2 = BukkitUtils.getOBCClass("CraftWorld");
It's important not to confuse the Updater's version methods with the BukkitUtils', as the first one refers to the versions of the plugin itself, whilst the second one to the Minecraft server version! BukkitUtils were implemented in 1.0-RC1 (so they will not work in versions below).
To create your own Serealizers, you can implement the JSONSerealizer<>
and/or YAMLSerealizer<>
interfaces in your own class!
SpigotAdditions has a built-in ItemSerealizer class since 0.3, that you can use to serealize ItemStacks to both JSON and YAML!
ItemSerealizer is = new ItemSerealizer();
ItemStack itemFromJson = is.fromJson(jsonString, PLUGININSTANCE);
String jsonFromItem = is.toJson(item);
ItemStack itemFromYaml = is.fromYaml(yamlString, PLUGINSTANCE);
String yamlFromItem = is.toYaml(item);
Introduced in 1.0-RC1, these classes are here to ease up your life and get rid of all that ugly boilerplate code!
PlayerUtils.sendActionBar(player, "Hello!");
PlayerUtils.hidePlayer(player) //will hide/vanish the selected player
PlayerUtils.banPlayer(player, "Ban Reason");
PlayerUtils.heal(player);
PlayerUtils.feed(player);
PlayerUtils.setMaxHealth(player, 30);
Block blockBelow = PlayerUtils.getBlockBelow(player);
PlayerUtils.sendPacket(player, PACKET);
UUID uuid = PlayerUtils.getOfflinePlayerUUID(playerName);
PlayerUtils.blockMovement(player);
And many more player util methods! The showPlayer()
and hidePlayer()
methods are even tied to a Listener, so that joining and leaving players won't bug out and see or unneedingly not see the player!
ItemUtils.setTag(item, "persistent-tag-key", "persistent-value", PLUGININSTANCE, PersistentDataType.STRING);
ItemUtils.addLore(item, "Added lore to existing item!");
ItemUtils.setName(item, "Sword of Testing");
ItemUtils.setCustomModelData(item, 1);
ItemUtils.setUnbreakable(true);
ItemUtils.setDurability(item, 100);
ItemUtils.clearPotionEffects(item);
ItemUtils.setBookPages(item, "Page 1");
ItemUtils.setBookAuthor(item, "ImDaMilan");
ItemUtils.isArmor(item);
ItemUtils.isTool(item);
ItemUtils.isFood(item);
ItemUtils.isCrop(item);
And many, many more! There are methods for modifying any type of ItemMeta - Books, Fireworks, Potions, Attributes, Enchants, ItemFlags, and so on! This is by far the longest Util class (and the above showcase is not even half of them!).
BukkitUtils.runAsync(() -> System.out.println("This is ran async!"));
BukkitUtils.getAsync(object, () -> object.setName("test")); //this is used for returning values async, not usually possible in async functions, could be great for returning SQL data for example, the value is returned through the object variable as a CompletableFuture
BukkitUtils.isVersionHigherThan("Selected MC version");
BukkitUtils.runTimerAsync(() -> System.out.println("This is ran async!"), 0L, 10L);
double serverTPS = BukkitUtils.getTPS();
String withColors = BukkitUtils.getColorCoded("$cWith a custom colorcode prefix!", '$');
More of these are shown above as part of the Version API! Plans for the future are to add more util classes, like EntityUtils, WorldUtils, etc.
You can use SpigotAdditions' Config API, introduced in 1.1, to save classes with static fields to a config file and easily manipulate those fields and file without messing with config classes and Spigot's YamlConfiguration and so on.
public @Config class MainConfig {
private static @Path("welcome-message") String welcomeMessage = "Welcome";
private static @Path String leaveMessage = "Goodbye!";
}
ConfigManager.saveToFile(PLUGININSTANCE, MainConfig.class);
The above will create the following structure in the config.yml file:
welcome-message: Welcome
leaveMessage: Goodbye!
You can set which file you want to save the config to by passing it to the Config annotation, @Config("test.yml")
, and keep in mind that if the Path annotation is empty, it the config member takes the name of the field, otherwhise, it takes the name of the parameter passed to it!
SpigotAdditions also supports a similar way of saving data to a YAML file.
public @DataFile("playerdata.yml") class PlayerProfile {
private @ObjectKey String playerName;
private @Path int level;
private @Path int strength;
private @Path int dexterity;
public static ArrayList<PlayerProfile> objects = new ArrayList<>();
public PlayerProfile() {}
public PlayerProfile(String playerName, int level, int strength, int dexterity) {
this.playerName = playerName;
this.level = level;
this.strength = strength;
this.dexterity = dexterity;
objects.add(this);
}
public static ArrayList<PlayerProfile> getObjects() {
return objects;
}
}
ConfigManager.saveToFile(PLUGININSTANCE, PlayerProfile.class);
ArrayList<PlayerProfile> profiles = ConfigManager.getFromFile(PLUGININSTANCE, PlayerProfile.class);
There's a lot to unpack here, so let's start off:
- The class must have the
@DataFile("file-path.yml")
annotation. - The class must have only 1
@ObjectKey
annotated field, that separates the objects in the file. - The
@Path
works the same as in the Config API. - The class must have an empty no-args constructor.
- The class must include a static
getObjects()
method that returns all objects instantiated.
An example of the output of a playerdata.yml
file would be:
ImDaMilan:
level: 100
strength: 100
dexterity: 150
Player2:
level: 50
strength: 10
dexterity: 100
ThirdPlayer:
level: 50
strength: 10
dexterity: 100
In the example above, we have 3 objects, where the object keys / names of the players are ImDaMilan, Player2 and ThirdPlayer, the level
variables are 100, 50 and 50, and so on.
Since 1.2, you can also add the 'separateFiles' boolean value to the DataFile annotation, in which case objects will have their separate files in a specified folder!
Kyori Adventure-based simple chat message API for sending easy colored messages with custom click actions, introduced in 1.2!
ChatMessage message = new ChatMessage("Hello world!");
message.onClick(player -> player.sendMessage("hey!"));
message.setColor(NamedTextColor.RED);
message.send(player);
Another awesome feature introduced in 1.2, this API allows you to listen to one event of a player, and than wait for that same player to trigger another event that you need!
new PlayerEventFlowCallback<PlayerInteractEvent, AsyncPlayerChatEvent>().listen(event -> {
event.getPlayer().sendMessage("send a message to be able to walk again");
PlayerUtils.blockMovement(event.getPlayer());
}).then(event -> {
event.getPlayer().sendMessage("here you go");
PlayerUtils.unblockMovement(event.getPlayer());
});
To use SpigotAdditions, you can use it as a dependency (or softdepend) in your plugin:
depend: [ SpigotAdditions ]
For the latest stable release use:
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<dependency>
<groupId>com.github.ImDaMilan</groupId>
<artifactId>SpigotAdditions</artifactId>
<version>1.2.2</version>
<scope>provided</scope>
</dependency>
For the latest development build use:
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<dependency>
<groupId>com.github.ImDaMilan</groupId>
<artifactId>SpigotAdditions</artifactId>
<version>1.3-beta.1</version>
<scope>provided</scope>
</dependency>
Keep in mind that development builds are not production-ready and should not be used outside of testing environments until the official release for those features appear!
For the latest stable release use:
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.ImDaMilan:SpigotAdditions:1.2.2'
}
For the latest development build use:
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.ImDaMilan:SpigotAdditions:1.3-beta.1'
}
Keep in mind that development builds are not production-ready and should not be used outside of testing environments until the official release for those features appear!
Big thank you to HSGamer on Spigot for helping with the command syncing for the Command annotation!