BFAPI has support for complex recipes making customising a server easier
These are used to identify what type of recipe you want to make
Type | Identifier |
---|---|
Shapeless Crafting | minecraft:shapeless_crafting |
Shaped Crafting | minecraft:shaped_crafting |
Recipes can have custom 'results' that are given to the player after they craft an item, for example xp can be given
Result ID | Description | Expects |
---|---|---|
xp | Gives the player the amount of xp levels specified | Integer |
run | A json object with two keys, server and player. Server is a JsonArray of commands the server should execute. Player is a JsonArray of commands the player should run | JsonObject |
advancements | A list of advancements identifiers to be awarded | JsonArray |
permissions | A list of permissions to be awarded | JsonArray |
items | A list of items to be given to the player | JsonArray |
messages | A list of Text to be sent to the player | JsonArray |
Recipes can have a plethora of requirements that are used to resrict access to a recipe
Requirement ID | Description | Expects |
---|---|---|
xp | Ensures the player has the amount of xp levels specified | Integer |
advancements | Ensures the player has the advancements specified | JsonArray |
permissions | Ensures the player has the permissions specified | JsonArray |
Adding custom requirements/features/types to recipes is simpler then it sounds, there are simple interfaces and methods that you can utilise to make it easier
Adding recipe requirements is very easy
To add a requirement you will need to use the addRequirementBuilder
method on AbstractRecipe
AbstractRecipe.addRequirementBuilder(); // like this!
The addRequirementBuilder
method takes two inputs, an id in the form of a string and an IRecipeRequirement
Here we are going to add a requirement that ensures the player has a specified amount of health
AbstractRecipe.addRequirementBuilder("healthRequirement", (jsonElement)->{
final int amount = jsonElement.getAsInt(); // we get the amount specified in the json here
return new IRecipeRequirement() { // we build an instance of IRecipeRequirement
@Override
public boolean canCraft(Field<Entity> crafter) { // this method controls if the player can craft the item
return crafter.getObject().getHealth() >= amount; // we are checking the the given entity has the health required to craft the item
}
@Override
public JsonObject addToObject(JsonObject object) { // here we build a serializer to allow for saving to json if the requirement is used in a recipe in a config
object.addProperty("healthRequirement", amount); // the id here should be the same as the one used in the `addRequirementBuilder` method
return object; // we need to return the object
}
};
});
Adding recipe result is as easy as a requirement
To add a requirement you will need to use the addResultBuilder
method on AbstractRecipe
AbstractRecipe.addResultBuilder(); // like this!
The addResultBuilder
method takes two inputs, an id in the form of a string and an IRecipeResult
Here we are going to add a result that kicks the player
AbstractRecipe.addResultBuilder("kick", (jsonElement)->{
final String message = jsonElement.getAsString(); // we get the kick reason specified in the json here
return new IRecipeResult() {
@Override
public void onCraft(Field<Entity> crafter) {
if (crafter.getObject() instanceof PlayerEntity pe) { // we ensure the crafter is a player
pr.networkConnection.disconnect(new LiteralText(message)); // we kick them with the message provided
}
}
@Override
public JsonObject addToObject(JsonObject object) {
object.add("kick", message); // like in IRecipeResult this should be the same id as used in addResultBuilder
return object;
}
};
});
Unlike adding requirements and results this takes alot to do
First of you need a working implementation of AbstractRecipe
that builds to the wanted recipe (you can look at the source for a better example of this)
Then you just need to register it with the addRecipeBuilder
on AbstractRecipe
Events have been changed to make them more modular, now all events must be registered and have an identifier
Just want a list of events? If so click me!
Creating an event is super simple, you don't even need to make it statically accessible!
To create a simple event we can do as follows
// First we need an identifier the mod will be under,
// it should always start with your modid if its generated by your event
// if its called from a mixin and is correspondent to a minecraft event you can use the minecraft identifier
// The identifiers path should be a simple name for the event, here we are going to make an event to handle
// and send internal messages: so we aptly name it message
Identifier identifier = new Identifier("MODID", "message");
// The class/type inside the Input<> is what will actually be sent over an event
// here we use a `String` as that is what we will send our message as
// The Event constructor takes in an identifier and a boolean
// the boolean dictates if the event should be automatically registered
Event<Input<String>> messageEvent = new Event<Input<String>>(identifier, true);
And that's it! Your message event has been created and can now be used by other developers
This example will talk about handling the event created in the prior example, so its recommended that you read it for context
The simplest way to handle an event is to directly add a handler to it
This can be done by either getting a static instance or grabbing it from the registry
So when you have an instance of the event how do you add a handler?
In this example I will be using the registry to get the event
// Using the registry to get the event
Event<Input<String>> messageEvent = (Event<Input<String>>) BFAPIRegistry.EVENTS.get(new Identifier("MODID", "message"));
messageEvent.addHandler((input)->{
// Here you can handle the event, any arguments will be passed through an event
});
All events should be registered to the BFAPIRegistry#EVENTS
registry, this means we can get an event by simply querying it with the events identifier
// It is vital that we use the same identifier as before
Identifier identifier = new Identifier("MODID", "message");
// When we pass the identifier to the registry we receive any event with the corresponding identifier
// however, since we get a generic event back we need to cast to the appropriate event
// Since we have to cast we need to use a unchecked suppression to avoid warnings
@SuppressWarnings("unchecked")
Event<Input<String>> messageEvent = (Event<Input<String>>) BFAPIRegistry.EVENTS.get(identifier);
If an event has a public access point you can simply grab it from there
// Just generic direct access
Event<Input<?>> example = ExampleEvent.INSTANCE;
If you don't want to get an event from the registry and don't have an access point you can use an EventListener to handle the event
Here I will still be using the event created in a prior example
public class ExampleListener implements EventListener {
public ExampleListener() {
// Here we auto register all events
// Doing it in the constructor removes complexity
register();
}
// We use an annotation to say what event to bind to
// The event must be on the BFAPIRegistry#EVENTS registry
@EventHandler(eventIdentifier = "MODID:message")
public void handleMessageEvent(Input<String> event) {
// You can handle the event like normal here
}
}
If you want a simpler approach to dealing with inputs then you can have an events input decomposed into the inner arguments
Here I will still be using the event created in a prior example
public class ExampleListener implements EventListener {
public ExampleListener() {
// Here we auto register all events
// Doing it in the constructor removes complexity
register();
}
// We use an annotation to say what event to bind to
// The event must be on the BFAPIRegistry#EVENTS registry
@EventHandler(eventIdentifier = "MODID:message", decomposeArguments = true)
public void handleMessageEvent(String message) {
// You can handle the event like normal here
// However you will notice that we now have decomposed/direct access to the inputs inner values
// this can make your code look cleaner when handling events
}
}
By default an event is not required, if it isn't bound to then it nothing will happen other than a simple log to console
You can modify this behaviour in the EventHandler
annotation as it has a few fields
Field | DataType | Description | Default |
---|---|---|---|
eventIdentifier | String | The identifer of the event in string form | N/A |
required | boolean | If the event is required, if it is an exception will be thrown if it fails to bind | false |
logOnFailedBinding | Boolean | If a message should be logged when the event fails to bind | true |
THIS IS OUT OF DATE, I have added alot of events since then and plan to update this documentation soon, however, I might move to githubs wiki feature for this
Minecraft events use the generic minecraft:id identifier
Event | Description | Identifer | Gives |
---|---|---|---|
CommandRegistrationEvent | Called when minecraft wishes to register all commands, you should use this to create commands | minecraft:command_registration | DualInput<CommandDispatcher<ServerCommandSource>, CommandManager.RegistrationEnvironment> |
BFAPI events use the generic bfapi:id identifier
Event | Description | Identifer | Gives |
---|---|---|---|
LoadedEvent | Called when BFAPI is finished loading | bfapi:loaded | Input<Loader> |
Any field/method/class marked by the @Deprecated
annotation will be removed after a month (roughly)
This is alongside any helper functions that utilise it
For example if a helper function takes in a depricated object as an argument it may be removed without being marked itself as it relies upon something that no longer exists. The method may not be marked itself
If a class/method no longer works as intended and cannot be repaired it may be removed without a deprecation notice