Technical 

Massiveplugin Development Basics

Starting with a disclaimer:
In this tutorial I won't teach you Java, nor will we have time to cover the Bukkit API. There is a good guide on how to start with Java, and there are good video classes or books that you can read to start with. You will need a jdk 7 or higher, a working IDE (integrated development environment), a compiled version of Spigot and being able to run that server.

This tutorial is for you if you are familiar with the Java syntax and OOP (object oriented programming) methodology, are aware of the basic structure of a Bukkit plugin and want to dive into plugin development with the help of MassiveCore.

Preparation
To work, modify and test the classes shown in this tutorial you need to download MassiveCore and MassiveBooks either by using git or downloading the zip from github. MassiveCore only needs the Spigot jar as a dependency and MassiveBooks needs the spigot jar and the MassiveCore project as dependencies.

A few links worth looking at:

For this tutorial we will be looking at and working with the plugin MassiveBooks. You might have read the Page on MassiveCraft.com, it will link the code you'll be seeing with what we as players can interact with.

MassiveBooks is a "fairly" simple, yet fully equipped MassivePlugin. It makes use of many of the components and thus is a good example.

Chapter 0 - The Main class

Bukkit plugins need atleast two things: A plugin.yml and a main class (class extending javaPlugin). The plugin.yml is also part of all MassivePlugins, and by convention our main classes are called the same as the project.

In our case the main class is located here:
72e89c09862525b9a80e94d0adb54794.png

The class MassiveBooks extends MassivePlugin. One of our tech standards is to use Singletons when applicable. In case you didn't know what a singleton is: At any time there is only one instance of said class present. This is usefull in our case here, as we don't want two instances of our main class.

Bukkit plugins have onEnable and onDisable methods, MassivePlugins makes this step a little easier for you with the onEnableInner method. The super class MassivePlugin saves when you started and when you stopped to enable your plugin, so you can see how long it takes.

Noticably, the activate method is what get's the stone rolling. Here we instantiate/activate classes so they are starting to work. We activate Collections (data storage), Commands and Engines (Event Listeners) and anything else we might need for the plugin to function properly.

In the further chapters of this guide you will learn about the other important parts of a MassivePlugin and eventually about substantial classes of MassiveCore.

Chapter 1 - Collections
To store data persistently (over restarts) we came up with the concept of Entities and Collections.
For example, let's look at the MBookColl:
ce42d70fb15f3095e89ceee8b705adba.png

It is a Singleton and extends Coll<MBook>. From that we know, it stores and manages all the MBook objects and files. Also, in your mstore folder (located in your directory you run the spigot server), a folder called massivebooks_mbook will appear and in it are the files representing the MBook objects.


MBook extends Entity. What is so special about it? All the fields in a class extending Entity are automatically persistent, as long as you put them in the load. You as a developer don't need to take care of storing/fetching data, the Collections handles that for you. To notify the collection that a value has changed code wise (through a setter for example), you must call changed. If you change a file directly, the changes will get loaded to the server in a matter of seconds.

This is the magic behind our powerful configuration system, where we can adapt config values on runtime and they will get applied within seconds rather after a restart.

Try it out

If you want to play around, add some extra field to MBook, don't forget to put them into the load method and see how it reflects in a file entry on your local folder and will persist over restarts.

Add getters and setters, make sure to call changed or see what happens if you forget to do that.
 
Last edited:
Starting with a disclaimer:
In this tutorial I won't teach you Java, nor will we have time to cover the Bukkit API. There is a good guide on how to start with Java, and there are good video classes or books that you can read to start with. You will need a jdk 7 or higher, a working IDE (integrated development environment), a compiled version of Spigot and being able to run that server.

This tutorial is for you if you are familiar with the Java syntax and OOP (object oriented programming) methodology, are aware of the basic structure of a Bukkit plugin and want to dive into plugin development with the help of MassiveCore.

Preparation
To work, modify and test the classes shown in this tutorial you need to download MassiveCore and MassiveBooks either by using git or downloading the zip from github. MassiveCore only needs the Spigot jar as a dependency and MassiveBooks needs the spigot jar and the MassiveCore project as dependencies.

A few links worth looking at:

For this tutorial we will be looking at and working with the plugin MassiveBooks. You might have read the Page on MassiveCraft.com, it will link the code you'll be seeing with what we as players can interact with.

MassiveBooks is a "fairly" simple, yet fully equipped MassivePlugin. It makes use of many of the components and thus is a good example.

Chapter 0 - The Main class

Bukkit plugins need atleast two things: A plugin.yml and a main class (class extending javaPlugin). The plugin.yml is also part of all MassivePlugins, and by convention our main classes are called the same as the project.

In our case the main class is located here:
72e89c09862525b9a80e94d0adb54794.png

The class MassiveBooks extends MassivePlugin. One of our tech standards is to use Singletons when applicable. In case you didn't know what a singleton is: At any time there is only one instance of said class present. This is usefull in our case here, as we don't want two instances of our main class.

Bukkit plugins have onEnable and onDisable methods, MassivePlugins makes this step a little easier for you with the onEnableInner method. The super class MassivePlugin saves when you started and when you stopped to enable your plugin, so you can see how long it takes.

Noticably, the activate method is what get's the stone rolling. Here we instantiate/activate classes so they are starting to work. We activate Collections (data storage), Commands and Engines (Event Listeners) and anything else we might need for the plugin to function properly.

In the further chapters of this guide you will learn about the other important parts of a MassivePlugin and eventually about substantial classes of MassiveCore.

Chapter 1 - Collections
To store data persistently (over restarts) we came up with the concept of Entities and Collections.
For example, let's look at the MBookColl:
ce42d70fb15f3095e89ceee8b705adba.png

It is a Singleton and extends Coll<MBook>. From that we know, it stores and manages all the MBook objects and files. Also, in your mstore folder (located in your directory you run the spigot server), a folder called massivebooks_mbook will appear and in it are the files representing the MBook objects.


MBook extends Entity. What is so special about it? All the fields in a class extending Entity are automatically persistent, as long as you put them in the load. You as a developer don't need to take care of storing/fetching data, the Collections handles that for you. To notify the collection that a value has changed code wise (through a setter for example), you must call changed. If you change a file directly, the changes will get loaded to the server in a matter of seconds.

This is the magic behind our powerful configuration system, where we can adapt config values on runtime and they will get applied within seconds rather after a restart.

Try it out

If you want to play around, add some extra field to MBook, don't forget to put them into the load method and see how it reflects in a file entry on your local folder and will persist over restarts.

Add getters and setters, make sure to call changed or see what happens if you forget to do that.
 
Last edited:
Chapter 2 - Commands

The Bukkit command system is really clumsy and isn't optimized for child commands.
(e.g: when /book is the parent command, /book unlock and /book clear are child commands)

Our Commands should share a consistent look, feel and function similarly. To achieve this, we created our own system on top of the one Bukkit provides.

  • /book <-- parent command
    • unlock < -- child command of book
    • clear
    • foo <-- child command of book, but also parent command
      • bar <-- child command of foo
      • zoot
    • ...

Child Commands
Let's take a look at a child command first:
3bbd957c20aa40cfb2d256dee16f9ff1.png

It consists of only two parts, the constructor and the perform method. You might have noticed that this class is not a singleton and should not be one either.

Constructor:
We set aliases, requirements and command specific fields in the constructor. In this case, there is only one alias "clear". Aliases are the commands names, the player can run the command by typing one of the aliases. (/books <alias>, e.g: /books clear)

Requirements can be anything, but they are usually used for permissions. So in this case, they need a permission called CLEAR and the need to be a player. The second requirement means, the console can't run this command.

Perform:
Here is where the action happens. In our example case it does get the item in hand and making it a blank book again.

Anything can happen in this perform method. You can grant a player 1000 Regals, heal them, teleport them or let them suffocate in cake.

To get the CommandSender (the one who "types" the command), we have the field sender which get's set right before the perform method is called. From this reference (if it is a player) you can get the persons inventory or do other logic with it.

Parent Commands
In MassiveBooks, there is only one parent command. It's too big for a screenshot so I will mention the line numbers on github. Singleton pattern again, we only really need one of these.

Starting on line 21 we declare all the child-commands as fields from this instance. and later in the constructor we add them using the method addChild.

A parent command does not override the perform method in most cases. Whenever you just type the parent command (e.g: /book) it will display you a help command of all available child-commands.

The third section (Override) shows you a second way to set a commands aliases. Whenever we want the aliases to be configurable, we place them in the MConf (Chapter 1 - Collections) and get their value whenever someone calls the method getAliases.

Try yourself

Now it is your turn. You can create a new child command and add it to the parent command. Play around with the perform method, send the CommandSender a message or see whether it is a player with (sender isntanceof Player) and then casting it. try it out and come back once you have done that.
 
Chapter 3 - Engines

You might have been wondering, players run commands to interact with the plugin, but how does Minecraft interact with it? The answer is Events. Events get thrown whenever something special happens. The PlayerFishEvent for example get's thrown when you right-click a fishing rod, just before you pull out a fish.

If we want to be notified about this, we need to "listen" for it. An Engine takes care of all the dirty work, you just have to say what you want to listen for and the engine will do all the listener registration/unregistration for you.

Engines are also Singletons. Apart from that, the listener part is identical to standard bukkit plugins.
51f7c70d756d937aa0a4374ad8a14646.png

The first listener listens to the PlayerJoinEvent on Priority MONITOR. Inside the method you have access to the event object and can read and sometimes write values.

Many Events are Cancellable, though this one isn't.

Grouping

Recently we started to split up our big Engines in multiple smaller ones. Each engine should just take care of one field. One could handle all Player related events or all events that activate a trap.

Try it yourself

Now it is your turn. Create a new Engine, give it a good name, make it a singleton, add it to the main classes activate method and listen to all the events that you want to. Ever wondered why redstone contraptions are bad for a servers health? Listen to redstone events and log how many times a second they fire when you build a big contraption. It will be an eye opener, trust me.
 
Last edited:
Wow a plugin coding tutorial!

First time I have seen a tutorial like this, keep up the great work!

(PS: This would have helped me so much 2 months ago :P)
 
Chapter 4 - Pager

Quite often you will want to display some sort of list in a command. You need a header to the page and of course have the content to be displayed nicely and to your liking. And since we do this so often (/f list, /lock list, /book list), we created the class Pager that takes care of almost all the tasks needed.

We are going to take a deeper look at the command /book list
a1f00fd85937016914cd2a602e4610f4.png

Note: The Pager extensively uses the generic concept introduced in Java 5. If you are unfamiliar with generics, I suggest you to read upon that first before proceeding.

As you can see, the Pager class has many constructors, to ease access in many situations. The fields are mostly clear (a command, a collection, etc), but what are Stringifiers or Msonifiers ?


In our example here, we use a Stringifier. Stringifier and Msonifier are generic "functional interfaces", you define them mostly as anonymous classes withing the parameters of a method/constructor. Generic (class<>) means, they can be constructed using any class. Much like you have a List<MBook> you have a Stringifer<MBook> here.

They have exactly one method, Stringifier --> String toString(T item, int index) / Msonifier --> Mson toMson(T item, int index)
T in our example is MBook. Essentially what these interfaces do, is to turn a item of the collection into your line of display in the page.

But why the hassle? Why not iterate over the given collection at once and turn them into msons/strings? The reason is performance.

Permormance excourse
Large collections (lists, sets, etc) are a threat to server performance if you apply a lot of logic to all the items. For example, if your factions collection has 12'000 Factions in it, simply fetching all this data takes its time already. But turning them all into displayable lines for a pager command is really demanding. Before we had the pager it was actually possible to create lag spikes through the command /f list.

The command didn't got executed often, but each time Tps dropped by 2-4. To avoid this risk, the pager needed to be able to do two things:
Only process the items in the collection that are actually displayed, message them in one packet (open only one connection over the internet) and do all of the logic async.

What is async you ask? Async is short for asynchronous and in terms of minecraft means, it doesn't run in the same thread as the main minecraft logic and thus doesn't affect Tps.

Since our Pager supports asynchronous processing, we didn't run into a single Tps issue anymore regarded to list commands.

Performance end

Let's go through the pager example one by one.

We create the pager by giving it the parameters this (the MassiveCommand we are in), the title of the page, the age number we want, the collection of items we want to process and the stringifier that turns these items into display lines (strings).

The pager now asynchronously calculates which range of items he'll need, turns them into display lines through the stringifier/msonifier and sends them along with the title as one packet to the sender of the command.


Try it yourself
You will need to create a new command at first and add it as a child command to it's parent.

Then decide which Collection you want to show (try it with a list of Integers that you fill yourself maybe) and how you want them displayed, using either a Stringifier or an Msonifier.

Mimic the rest of the book list command and compile the plugin.
 
Chapter 5 - Mson
Just last chapter we talked about the Msonfier, a generic functional interface that offers the method toMson which turns a list item into a Mson. We also heard that these Msons can be messaged just as easy as a string. But what are Msons actually and is the last sentence actually true?

History
Json is a data storage format native to the JavaScript programming language (Java and javaScript have nothing in common), which stores all object fields to a string based representation.

For a long time it was only possible to send Strings from the Server to the client, but with version 1.8 Minecraft introduced "raw text", which were json-strings being sent. The main purpose (so Mojang thought) was to enable servers to send colored messages. If you remember, we were actually able to do that before "raw text", thanks to color codes.
But the feature that shipped with "raw text" was the ability to create clickable lines. With this feature, you could run commands, open links or suggest text input for the player by clicking on a piece of text on the screen.

And this feature was the milestone we were waiting for.

Own class
Mojang had not intended servers to use their raw text that as we planned to do. The only way to send raw text was either running a console command or send a packet. Sending packets directly means using NMS (net.minecraft.server) code (decompiled minecraft source) and is thus dangerous, but highly efficient.

And how did mojang intend us to send them? By typing in the json-string in all by ourselves.
For a vanilla server that might be sufficient, but it isn't for a server like ours.

So in essence, we needed to create a class that is handy to use, handles all the "to raw text" conversion for us and let's us send those messages easily over our existing framework.

Mson is the short form for Messaged Json, our very own implementation of the raw text feature of Mojang.

Basics

A Mson consists of a text, color and event code.
  • Text is the String component, the letters a player sees when they get the mson sent to their client.
  • Color is (obviously) in which color the message get's displayed.
  • Event Code is where this get's tricky. This can be click-events and hover events.
    • click-events happen when the player clicks on the mson (can run commands, open links ...)
    • hover-events are what get's shown to the player when they hover over the message with their mouse
Most of the time, you will need a very basic Mson. To display the message "Hello world" in bright red, you will need to send this to a player:
Code:
Mson mson = Mson.mson("Hello world").color(ChatColor.RED);

You have probably noticed that the factory Mson.mson() returned a Mson object, but did you also notice that the method color was simply concatenated to the construction and also returned a Mson object?

This is because we use a "pseudo" builder pattern. Msons are actually immutable, so this part the factory method creates one Mson and whenever you use a method (here color), it creates a new Mson with the given property that has changed. This is important to know.

To illustrate this, we compare Msons to Strings, as Strings are also immutable.
Code:
// Strings
String hello = "hello world";
hello = ChatColor.RED.toString() + hello;
hello = hello + "!";

// Mson
Mson helloMson = Mson.mson("hello world");
helloMson =helloMson.color(ChatColor.RED);
helloMson =helloMson.add("!");

// Mson, doesn't work
Mson helloMson2 = Mson.mson("hello world");
helloMson2.color(ChatColor.RED);
helloMson2.add("!");
// This doesn't work as Msons are not mutable

Now that we know how we can create and modify Msons correctly, we can move in to the detail.

Intermediate

Each Mson object has one color, one click event, one hover event ... one of everything. But what if you want to have multiple click events on the same line, like we did on the pager where you can click the next and last page buttons?

Msons have one field called "extra", which is a list of other Msons (Extra was the name Mojang gave it, not our choice). This way, we can have one parent mson that has a lot of extra.
  • Mson.mson("Hello") <-- parent mson
    • Mson.mson(" World") <-- extra
    • Mson.mson("!") <-- extra
If you now compose this mson you will get the message "Hello Word!" displayed on your screen.

Each extra can have it's own color, it's own click event and so forth, it is a fully functional standalone Mson. But put together, we can really create great usability features.

To add a Mson to anothers extra, call
Code:
Mson.mson("hello").add(" world");

Overloading

If you take a closer look at the methods in Mson, you will see that many of them are overloaded (same name and return type used with multiple parameter sets). As in Add, you can pass any Object, varargs of object or iterables of object to many methods that create/add Msons. Our method Mson.mson(Object) processes them and turns them into a nice Mson.

Tl; dr; it doesn't matter whether you do Mson.mson("Hello").add(" it's me") ; or Mson.mson("Hello").add(Mson.mson(" it's me"));
If the method accepts objects, use strings or msons whichever suits the situation the best.

Usage

To come back to the question at the very beginning, it is almost as easy to send Msons as Strings to a player.

MassiveCore has a class that takes care of sending messages (Msons or Strings) to a player, which is called MixinMessage. With it's methods, sending get's as easy as one line of code:
Code:
MixinMessage.get().messageOne(player, mson);

Try it yourself

In this chapter I showed you all you need to know to construct and send an Mson to a player. You can use it inside of an event or a command for easy testing.
 
Chapter 6 - Integrations

Often times we want to be aware of what other plugins are being used and on special occasions we want to interact with them.
Bukkit offered so called "soft dependencies", you can have them but they are not needed.

For most developers and plugins this actually suffices, but there are other use-cases where an integration can be practical.

Conditional Code

Every developer knows the if - else statement. If something is given, do something. And most of the time when working with classes and objects you know this is just fine. But what if the run-time environment (the server) has no reference to the class you are requesting? Meaning, the third party plugin you want to hook into is non-existent.

Here is where nasty ClassNotFoundExceptions get thrown and interrupt your code-flow and it's execution.

To give the players a nice experience we as the developers need to catch this error. For a long time, we actually did this. Wrap all the code in a big try/catch block and expect it to fail.

What if the Exception would never get thrown at all? What if we could check if the classes/plugin is around before anything code is run? This is what sparked the Integration class.

Activation

When an Integration class is activated (In the main class), it will check upon two pre-condition:
  • The Plugin name(s)
  • The Class names

If you wait for a different plugin, you set the plugins name with setPluginName(String name).
If you for example want an Engine to only activate when the server is on a 1.9 version, we can setClassName(String name) to a classes name that is only present in Minecraft 1.9.

If all plugins are present and all classes can be found, only then it will activate the integration.

You can also check from within code whether a Integration is active or not with isIntegrationActive(). This way you can excecute additional code in your usual classes if an integration is active


Code:
if (IntegrationMassiveMagic.get().isIntegrationActive())
{
    EngineMassiveMagic.get().doSomethingMagical();
}

Integration Engines

The usual case for Integrations is to listen to a plugins events or to events that are only present in a very high API level / server version.

For example: Factions must support any server on version 1.7.10 and above. So all events added from 1.8 - 1.10 need to be put in separate Engines., as they can't be activated like the other Engines. They can only be activated by an Integration which checks for the precondition (whether the server version is high enough).

Factions has the IntegrationSpigot and EngineSpigot, because at some point Spigot decided to change the behavior of 2 events.

Try it yourself

Do you have a plugin yourself and would love if Factions would hook into one of your events? Or do you want to see what happens if you combine the efforts of two plugins?

Take a look at existing Integrations, create a new Engine and add your listeners or conditional code.
 
Chapter 7 - Util classes

Part 1) Txt.java

In Chapter 5 we talked about Msons and how we were actually able to send colored messages before 1.8. And to be honest, we love our colored messages don't we?

One of the very early classes In MassiveCore was Txt, it offers a wide range of String manipulation methods. I will present to you the most used methods from this class.

Parse

Minecraft can interpret colors in plain text, but we need the identifier § + hex number. It is rather inconvenient to always search for the correct color code which you want, so the method parse changes our human readable tags and turns them into minecraft color codes.

Example: Txt.parse("<green>Hello <red>World, <aqua> I command you to do this") = Hello World, I command you to do this
The full list of tags that can be translated can be found here.

You can also use Parse with an integrated method of String.format. If you are unfamiliar with String.format, you might want to read up on that first before using the Txt.parse(String string, Object... objects);

But it essentially replaces certain %-tags with your object. (%d = digit, %s = String
Ex: Txt.parse("There are %d people on planet %s.", 5, "mars") = "There are 5 people on planet mars."

Of course, you can use both % and <color> tags at the same time, they won't interfere with one another.

UpperCase- / LowerCaseFirst

There are a lot of different places where the first letter needs to be capitalized, most prominently the first letter in a sentence. the method UpperCaseFirst is quite self explanatory, it just returns the same String except the first character was capitalized.

Implode

We all not what happens when an object explodes. After the explosion there are several parts. So in the function Implode we do the opposite, we take many parts (a collection or an array) and glue them together in one String.

Ex. Collection<String> collection = new MassiveList<String>("Hello", "Humans", "aliens");
Txt.implode(collection, " "); = "Hello Humans aliens" <== glued together with a space
Txt.implodeCommaAnd(collection) = "Hello, Humans and aliens." <== glued together with comma and a space, plus last command replaced with "and".

The method implode is heavily overloaded, so you can use it the way you need it to behave.

Aan

Words that start with a vowel (ex. item) should be addressed as "an item" instead of "a item".When automatically constructing messages from Collections it is usefull to have the aan method, this way we don't need to know in beforehand whether the word starts with a vowel or not.

getNicedEnum(String)

Enums are great to group literals together. The most prominent Enum in Minecraft are the Materials. If you call enum.toString() however, you get literally just how the enum was written.

Ex: Material.STONE.toString() = "STONE"

But with Txt.getNicedEnum() you'd actually get a nicely formated String from any Enum. Uppercase the first letter, lowercase all others.

Ex: Txt.getNicedEnum(Material.STONE) = "Stone"

CreateItemMson

Have you ever used the {item} tag on MassiveCraft? You totally should, because that gives us the perfect example to what this method does.

You give it an ItemStack and get back an Mson. This Mson will look soemthing like this "[ItemName]" and have a hover-event that displays all the lore lines of the item plus its title. There is no easier way for you to get that Package ;).

Titleize

We have talked about paging before in Pager. Parts from the pager logic (like creating the title) is handled in this section. There is probably no need for you anymore to understand what is going on int depth anymore (since we have the Pager), but it could be worth to look through it and see what happens behind the curtains.

Part 2) MUtil.java

You are maybe asking yourself, why this is called Mutil. Well, M is usually the abbreviation for MasssiveCraft and Util stands for Utility classes....so what job does MUtil do?

Everything that is not big enough to group it in it's own utility class.

With this said, you will be able to find a vast number of methods in MUtil, most of which you will barely ever use but are still useful to be centralized.

Let's just jump right into the notable methods:

Stacktrace debug

Stacktraces are a developers best friend in debuging within Minecraft. Whenever a error get's thrown Minecraft catches it and shows you its stacktrace. In a stacktrace you can read all the methods who were called, which line went to the next method and ultimately which line causes the problem.

But sometimes, you don't have an error or you don't know why and by who a certain method of yours gets called. Then you can use the stacktraceDebug(String name) function to find the exact way this method call was executed.

setDamage

Bukkits Damage Events are not very easy to modify. If you simply want the damage to be set to one specific number, you will need to use this method in MUtil. It covers all the complex bukkit behavior and is easy to use.

isSameBlock/Chunk

The player move events get triggered really often, even if you just rotate your head. So to reduce stress on the event, you can check whether it is the same block or chunk. For the usual case it is enough to update a value when a player has moved a full block.

Examples: Changing Faction territory (isSameChunk), CreativeGates teleportation (isSameBlock)

Simple Collection Contructors

Often when creating a Collection (set/list) you don't exactly know how many there will be or you might want to be able to change that easily in the future. With the simple constructors we take in a vararg (can be 1, 2, 3 ... objects of a type) of an object and return a list.

Ex: List<String> movies= MUtil.list("Wall-E", "The Avengers", "Harry Potter");

If we need to add another movie to the constructor later on, we can just add a comm and the next name. No need to do movies.add("Rapunzel");

Random from a Collection

If you plan on doing a lottery or coding anything involving picking a random subset/item of a list, these random methods are just right for you.

We have used generics to be able to handle all object types equally.

Equals

Speaking of equality, we have created this handy shortcut for you to compare two or multiples of two object for equality. The convenience here is that we already handle the case where one (or both) objects are NULL.

Probability Round

Rounding double to int/longs can be unpleasant. What do you do actually? Round up? round down?
Here is where Probability rounding comes into play. It takes the full number (Ex: 3.5 ==> 3) and uses the remaining (0.5) as the probability to round up. It now gets a random number between 0 and 1, and if the probability is higher then the random number, we round up.

Note: Don't use this if you need a deterministic outcome of a method.

Part 3) IdUtil.java

Coming soon...
 
Last edited:
Chapter 8 - Types

A good part of developing new commands is to understand what Types are. In chapter 2, we had a very basic command that didn't require the user to specify an argument, it was simply /book clear.
Probably in all plugins you will want a bit more complex commands, such which prompt the users to specify a Faction to look at (/f f <faction>) or a player to use (/f p <player>); That is where Types are used.

A Type is (not only, but for now) an argument reader, it turns a players input (a string) into some other object. This can be a player, a faction, a number or any Java-Object really.
Types are generic, there can be a Type<Double>, a Type<MPlayer>, a Type<Faction> and so on. In the classes head you will see Type<T>, where T is the generic type it will convert to and do other things with.

The Type does this conversion by reading the argument of the player. So a Type<Double>'s read method takes a string and the sender as parameters and returns a Double.

Reading arguments

Every Type must override the read method. In this read method you might want to do some checks, see if this string actually makes sense in your context, then convert it and eventually check whether the Object is actually within the allowed boundaries.

Possible check Examples:

  • Type<Player>: Is it an actual name?
  • Type<Vampire> extends Type<Player>: Same as Player.
  • Type<Double>: Is this string an actual number?
Conversion Examples:
  • Type<Player>: Player player = IdUtil.getPlayer(arg);
  • Type<Vampire>: Same as Type<Player>
  • Type<Double>: Double.parseDouble(arg);
Boundaries check Example:
  • Type<Player>: Is Player offline/online?
  • Type<Vampire>: Is the player actually a Vampire?
  • Type<Double>: Maybe the result must be positive? Or in between two values?

Full example:
Code:
import java.util.List;

import org.bukkit.command.CommandSender;

import com.massivecraft.massivecore.MassiveException;
import com.massivecraft.massivecore.collections.MassiveList;
import com.massivecraft.massivecore.command.type.TypeAbstract;
import com.massivecraft.massivecore.util.MUtil;
import com.massivecraft.massivecore.util.Txt;

public class TypeLimitedDouble extends TypeAbstract<Double>
{
   // -------------------------------------------- //
   // FIELDS
   // -------------------------------------------- //
 
   private double min;
 
   private double max;
 
   // -------------------------------------------- //
   // CONSTRUCT
   // -------------------------------------------- //
 
   public static TypeLimitedDouble get(double min, double max)
   {
      return new TypeLimitedDouble(min, max);
   }
 
   private TypeLimitedDouble(double min, double max)
   {
      super(Double.class);
      this.min = min;
      this.max = max;
   }
 
   // -------------------------------------------- //
   // OVERRIDE
   // -------------------------------------------- //
 
   @Override
   public Double read(String arg, CommandSender sender) throws MassiveException
   {
      // Read Double
      Double ret;
      try
      {
         ret = Double.parseDouble(arg);
      }
      catch (Exception ex)
      {
         throw new MassiveException().addMessage(Txt.parse("<b>\"<h>%s<b>\" is not a number.", arg));
      }
    
      // Limit
      double amount = MUtil.limitNumber(ret, min, max);
      if (ret.compareTo(amount) == 0) return ret;
    
      // Throw if not within limits
      throw new MassiveException().addMsg("<b>Number must be between %.1f and %.1f", min, max);
   }
 
   @Override
   public List<String> getTabList(CommandSender sender, String arg)
   {
      return new MassiveList<>();
   }
 
}[/SIZE]


As you can see, we omit checks by using a try/catch block around Double#parseDouble. If it's not a double, we throw the MassiveException whichs text get's prompted to the player who issues the command.

After that, we try to limit the range of the number and see whether the new and old one is still the same. If it is, return the value and if it's not, throw another Exception.

MassiveException in Types let's us stop a commands execution even before any of the command logic itself is started. This is good for security reasons. :)


Tab Completion


In the above example you see that the method getTabList is overriden, but in our example it simply returns an empty list.

Tab completion is the best invention to MineCrafts chat system since colors, but it is normally really tedious to use. We have made it as simple as possible for you and included this method within the type. All the technical parts which forward the list to the players is taken care of by lower levels of the code.

What would you want to tab-complete, you might ask. As far as vanilla Minecraft goes, you can tab-complete names everywhere. But in commands, you also might want to tab-complete faction names, true and false for a boolean, any enum or otherwise finite namespace.

Tab completion really makes it obsolete to enable short arguments for things. You no longer have to give the players the option to do "f-{name}", since if they type f and hit tab, it will complete it for them to "faction-" for example.

Visualization


A Type can also be used to visualize the Object it takes care of reading.

The Type name:
A human friendly name that describes what this Type converts/handles. For TypeFaction this would be "Faction" and for a TypeDouble this would be "Number".

The Objects name:
The name is, as it's written in the code, a human friendly but simple representation without color and clutter.
For example, a Faction might be represented by it's Faction-name.

The Objects visual Color:
In case we are describing something that has a color (Wool for example), you can show it using this method.

The Objects visual:
If you don't want to use the standard implementation in TypeAbstract , you can override this method and use your very own style of doing the visual.


All in all, this visual is immensely important for players, as this is what they will see and interact with.
 
Last edited:
Hey @ulumulu1510 ,

I was playing around with MassiveCore last night, and made a test plugin to mess around with entities, but was having an issue with getting the plugin running. I'm assuming it's because spigot tried to load my plugin using spigot's methods/whatever, rather than MassiveCore's, as the log showed spigot attempting to load my plugin before MassiveCore was ever touched (the log is here: https://pastebin.com/fE8BAm5v, main class is here: https://pastebin.com/QthWTiDr). However, in my plugin.yml I do have the line "depend: [MassiveCore]", minus the quotes. So I'm assuming I'm missing something stupidly simple, but I've been messing with it for the last half hour and haven't managed to fix it, and would appreciate some help, haha. Thanks in advance.
 
Hey @ulumulu1510 ,

I was playing around with MassiveCore last night, and made a test plugin to mess around with entities, but was having an issue with getting the plugin running. I'm assuming it's because spigot tried to load my plugin using spigot's methods/whatever, rather than MassiveCore's, as the log showed spigot attempting to load my plugin before MassiveCore was ever touched (the log is here: https://pastebin.com/fE8BAm5v, main class is here: https://pastebin.com/QthWTiDr). However, in my plugin.yml I do have the line "depend: [MassiveCore]", minus the quotes. So I'm assuming I'm missing something stupidly simple, but I've been messing with it for the last half hour and haven't managed to fix it, and would appreciate some help, haha. Thanks in advance.
Nope, I'm an idiot ignore this lol. Apparently I can't remember to write the word public before constructors haha
 
I've become a huge fan of your guys' work on MassiveCore and your related plugins after exploring through the source code, reading about the troubles you encountered in comments/commit message, and seeing how you built a rather elegant system to reduce boilerplate code and unnecessary complexity.

Before looking to some of MassiveCraft's code for inspiration and knowledge, I was (and will continue to build) a system for my own plugins that works similarly, but is more specific to my server's custom requirements. I wasn't actually attempting to imitate the way MassiveCraft did things, but when I looked through the code, I realized MassiveCraft already accomplished a lot of what I was aiming to build.

Now, to my question.

Some of my plugins depended on the internal Ebeans server for persistence. With the release of 1.12, MD_5 decided to remove the internal server. Consequently, some of my plugins have become broken. I planned on moving away from Ebeans to a JSON-based storage method anyway, so I decided to utilize MassiveCraft's Entity based collection system.

I believe I've implemented everything properly, for the most part, but I'm having trouble with the SenderColl. I didn't read this anywhere in the documentation, so I may be wrong about this, but it would appear that SenderColl Entities are created automatically when players log in, as long as the class extending SenderColll is activated in the main class. However, this appears not to be the case. Are they created automatically, or will I need to do that manually in a OnPlayerJoin event?

My reason for thinking they are built automatically is because I've tried looking through Factions/MassiveCore source code to see how MPlayers are created and attached, but I've found no code that seems to perform that function. That caused me to believe MassiveCore itself may watch for Colls that extend SenderColl and update the Entities accordingly.

Any help in this regard would be greatly appreciated. Thank you all once again for all the work you've put into MassiveCore. I enjoy the artistic beauty that's present in the code.

My best regards,

- Quietboxcrafter