Own Component - ParzivalExe/guiapi GitHub Wiki
You have finally come to the end-boss, creating your own Components. In this Chapter I will first tell you how you can create your own component and give them a specific logic, then how to update this component so that it also supports XML.
Every Component you create must in some way inherit from Component
, so your first step is to create a new Class and extend
it from Component
public class ExecuteCommandComponent extends Component {
}
As you might already guess from the Class-Name, we want to create a Component
, that will perform a specific Command for a Player when he clicks the Component
.
Next we must create the Constructor
for your new Component and also implement the parent constructor with a super
-call.
public ExecuteCommandComponent(ComponentMeta meta) {
super(meta);
}
Now, we must implement the one abstract function given in Component: componentClicked(...)
@Override
public void componentClicked(HumanEntity whoClicked, Gui gui, InventoryAction action, int slot, ClickType clickType) {
//Content
gui.closeGui();
}
As you might have already guessed, this function is always called, when the Component is clicked, so this is the function where most of the logic must be implemented. So let's start with exactly that.
if(whoClicked instanceof Player) {
if(command != null) {
((Player) whoClicked).performCommand(command);
}else{
whoClicked.sendMessage("This component has no Command implemented right now");
}
}else{
whoClicked.sendMessage("Normally, only players can click Components");
}
Now, as you might already have noticed, command
is not yet implemented. Therefore we also have to create a new field command
private String command;
and add the getter and setter for this field
public void setCommand(String command) {
this.command = command;
}
public String getCommand() {
return command;
}
It might also be a good idea to overhaul your constructor
and create a completely new one so that we can create this component and give the command
for it directly when we create it
public ExecuteCommandComponent(ComponentMeta meta, String command) {
super(meta);
this.command = command;
}
public ExecuteCommandComponent(ComponentMeta meta) {
this(meta, null);
}
Great, we have now finished creating a component
. You can test this component
by adding it to the Gui
like any other Component
that comes default with GuiAPI.
ComponentMeta commandMeta = new ComponentMeta("GameMode 1", new ItemStack(Material.COMMAND));
ExecuteCommandComponent commandComponent = new ExecuteCommandComponent(commandMeta, "gamemode 1");
gui.addComponent(commandComponent);
The whole class should now look like this:
public class ExecuteCommandComponent extends Component{
private String command;
public ExecuteCommandComponent(ComponentMeta meta, String command) {
super(meta);
this.command = command;
}
public ExecuteCommandComponent(ComponentMeta meta) {
this(meta, null);
}
public ExecuteCommandComponent() {
this(new ComponentMeta("", new ItemStack(Material.WOOL)));
}
@Override
public void componentClicked(HumanEntity whoClicked, Gui gui, InventoryAction action, int slot, ClickType clickType) {
if(whoClicked instanceof Player) {
if(command != null) {
((Player) whoClicked).performCommand(command);
}else{
whoClicked.sendMessage("This component has no Command implemented right now");
}
}else{
whoClicked.sendMessage("Normally, only players can click Components");
}
}
public void setCommand(String command) {
this.command = command;
}
public String getCommand() {
return command;
}
}
Now, we just need to get this Component XML-Ready. As this is a very simple Component with only one string-field, this is very easy to implement.
First you need to implement a public constructor without any arguments, because this is needed to create this Component through XML. So, we add the following constructor:
public ExecuteCommandComponent() {
this(new ComponentMeta("", new ItemStack(Material.WOOL)));
}
Now, all you got to do is write @XMLAttribute
above private String command
.
The Annotation
XMLAttribute
does pretty much what it's name suggests. It makes any field
into an attribute
for XML-Code. This annotation also has got some variables we can change to customise this attribute further.
-
necessary (boolean): With this you can specify whether this attribute is necessary for this Component to work. If this value is true and an
ExecuteCommandComponent
-Element in XML doesn't have this attribute implemented, the component will throw anException
and not be created. -
attrName (String): specifies the
name
of thisattribute
. If this isn't set at all, thename
will be the exact same as thefield
itself. -
defaultValue (String): specifies the default-value used if this
attribute
is not implemented as aString
(because in xml values are always text/string before converted). This will be used when theattribute
is not implemented. If you don't set thedefaultValue
and theattribute
is not implemented, it will simply ignore thisfield
completely. -
converter (Class<? extends Converter>): specifies a
Converter
. Thisconverter
is needed for anyfield
that isn't a primitive-type (int, String, float, double, short, long, byte, char, boolean). While primitives are converted from the string given in XML automatically, any otherobject
can't be created from this String. I will show more later under the Converter-Part of this Tutorial. - forceEndType (Class<?>): specifies the EndType. This is normally simply the type of the given field and is not needed until further into the chapter by which case it will be clearer how this variable works.
Well, after looking at all these variables, I would suggest, that command
is needed for this Component to work and should therefore be changed to necessary
, so we must change @XMLAttribute
to @XMLAttribute(necessary = true)
.
So, the implementation of your Component
would right now look something like this:
<ExecuteCommandComponent title="GameMode 1" look="137" command="gamemode 1"/>
Now, let's start changing this up a bit more to bring more functions and more complexity to this Component so that I can show off some more options for XML.
Let's start by making command
its own object
. Here is the object
we are going to start with:
public class Command {
private String command;
private ArrayList<String> arguments;
public Command(String command, ArrayList<String> arguments) {
this.command = command;
this.arguments = arguments;
}
public Command(String command) {
this(command, new ArrayList<String>());
}
public String buildCommand() {
StringBuilder command = new StringBuilder(getCommand());
for(String argument : arguments) {
command.append(" ").append(argument);
}
return command.toString();
}
//region Setter
public void setCommand(String command) {
this.command = command;
}
public void setArguments(ArrayList<String> arguments) {
this.arguments = arguments;
}
public void addArgument(String argument) {
arguments.add(argument);
}
public void addArgument(String argument, int index) {
arguments.add(index, argument);
}
//endregion
//region Getter
public String getCommand() {
return command;
}
public ArrayList<String> getArguments() {
return arguments;
}
public String getArgumentAtIndex(int index) {
return arguments.get(index);
}
//endregion
}
Now we also need to update the Command
-Field
@XMLAttribute(necessary = true)
private String command;
... to ...
@XMLConstructor(constructorAttributes = {
@XMLAttribute(necessary = true, attrName = "command", forceEndType = String.class),
@XMLAttribute(attrName = "arguments", forceEndType = String.class)
})
private Command command;
As you see, we can also create Objects
through their Constructor
. What is important to realise here is, that we always need to specify the attrName
for every single attribute
because in Java-Reflections you simply can't get the name of a constructor-argument.
It is also important that every possible combination of these XMLAttribute
s needs to have a matching constructor
. That's why I made command necessary = true
, because now we have two possible combinations ...
<ExecuteCommandComponent title="GameMode 1" look="137" command="gamemode"/>
... and ...
<ExecuteCommandComponent title="GameMode 1" look="137" command="gamemode" arguments="[1]"/>
... and both of them have a matching constructor
with ...
public Command(String command) {
this(command, new ArrayList<String>());
}
... and ...
public Command(String command, ArrayList<String> arguments) {
this.command = command;
this.arguments = arguments;
}
Would we make arguments necessary = true
as well, we only needed the Constructor
with two arguments while making command necessary = false
we needed a third Constructor
without any arguments.
In addition to that, you can also see, that for this the ForceEndType
is needed, since the Constructor
is picked from these Types but importantly, you can also see that for ArrayList
s, List
s or Array
s you need to specify the inner type
(for example: for ArrayList<String>
the ForceEndType
is String
.
XMLContent
is a annotation that is also used for fields, just like XMLAttribute
and XMLConstructor
but, as the name already suggests, this time to mark that the value
associated with this field
should be found as Content
of the Element
representing this Component
. As an example the newOpenGui
-Field for the Folder
-Component.
@XMLContent(necessary = true)
private Gui newOpenGui;
As you can see, for this Annotation there is only one value: necessary
.
The Content of this Element
is created the same way as Component
s, meaning, you could create your very own Class
and change it so it can become a XML-Element
.
We could change command
inside ExecuteCommandComponent
from XMLConstructor
to XMLContent
IF we changed Command
itself accordingly so that it could be created in XML. This would look something like this:
public class Command {
@XMLAttribute(necessary = true)
private String command;
@XMLAttribute
private ArrayList<String> arguments;
public Command(String command, ArrayList<String> arguments) {
this.command = command;
this.arguments = arguments;
}
public Command(String command) {
this(command, new ArrayList<String>());
}
public Command() {
this(null);
}
...
}
As you can see, we have to add the XMLAttribute
-Annotation and add a Constructor
without any arguments.
Now we only need to change the ExecuteCommandComponent
accordingly:
public class ExecuteCommandComponent extends Component{
@XMLContent(necessary = true)
private ArrayList<Command> commands = new ArrayList<>();
public ExecuteCommandComponent(ComponentMeta meta, ArrayList<Command> commands) {
super(meta);
this.commands = commands;
}
public ExecuteCommandComponent(ComponentMeta meta, Command command) {
super(meta);
if(command != null) {
this.commands.add(command);
}
}
public ExecuteCommandComponent(ComponentMeta meta) {
this(meta, (Command) null);
}
public ExecuteCommandComponent() {
this(new ComponentMeta("", new ItemStack(Material.WOOL)));
}
@Override
public void componentClicked(HumanEntity whoClicked, Gui gui, InventoryAction action, int slot, ClickType clickType) {
if(whoClicked instanceof Player) {
if(commands != null || commands.size() > 0) {
for(Command command : commands) {
((Player) whoClicked).performCommand(command.buildCommand());
}
}else{
whoClicked.sendMessage("This component has no Commands implemented right now");
}
}else{
whoClicked.sendMessage("Normally, only players can click Components");
}
gui.closeGui();
}
public void addCommand(Command command) {
this.commands.add(command);
}
public void addCommands(Collection<Command> commands) {
this.commands.addAll(commands);
}
public void setCommands(ArrayList<Command> commands) {
this.commands = commands;
}
public void clearCommands() {
commands.clear();
}
public ArrayList<Command> getCommands() {
return commands;
}
}
As you can see, I have taken this opportunity to change up this component a bit as it is now able to perform several Commands
.
For Implementation in Gui
we must first Include
the Command
-Class and can then create the Component
just like this:
<ExecuteCommandComponent title="Debug Setup" look="3x137" description="[Performs:, /gamemode 1, /time set 0, /weather clear]">
<Command command="gamemode" arguments="[1]"/>
<Command command="time" arguments="[set, 0]"/>
<Command command="weather" arguments="[clear]"/>
</ExecuteCommandComponent>
You can also leave out all the Command
-Elements and try it again and it should (because at least one Command
-Element as Content
is necessary
) fail.
Lastly we come to the Converter
. As an easy easy example, I will use the OpenOptionConverter
which is used for AdditionalOptionsComponent
.
public class OpenOptionConverter implements Converter {
@Override
public Object attributeStringToValue(String attrString, Object defaultValue) {
if(attrString.equalsIgnoreCase("newInventory"))
return OpenOption.NEW_INVENTORY;
else if(attrString.equalsIgnoreCase("underInventory"))
return OpenOption.UNDER_INVENTORY;
else
return defaultValue;
}
}
As you can see, creating your own Converter
is easy, as is using it. All you need to do is give his class as the variable converter of XMLAttribute
:
@XMLAttribute(converter = OpenOptionConverter.class)
public OpenOption openOption = OpenOption.UNDER_INVENTORY;
And that's it. Now a String like openOption="NewInventory"
will be changed to OpenOption.NEW_INVENTORY
.
Now, that's it. YOU HAVE SURVIVED 🎊🎉
No really, if you really got to this point, that's one hell of an achievement. I know this API doesn't belong to the easiest to learn, but once you mastered it, you will never want to go without it, just like I never develop a Plugin without using it nowadays, which was kind of the idea in the beginning in general, because just making a UI was simply to complicated for me.
Well, you can now proceed to End & Help if you want to. If not, that is fully understandable 😅
Component-Class: io.github.parzivalExe.guiApi.components.Component
XML-Annotations: io.github.parzivalExe.guiApi.antlr.interfaces.*
Example-Component (in Java): io.github.parzivalExe.guiApi.components.ExecuteCommandComponent
Example-Command-Class (in Java): io.github.parzivalExe.guiApi.objects.Command