-
Notifications
You must be signed in to change notification settings - Fork 0
API: registries
A lot of elements can be registered in this plugin.
To register a java-function, you will need to provide :
- A definition for the DSL,
- An executable code. Then, what remain will be a registration, using the JavaFunctionProvider # register method.
Those 2 things are united in one class : RunnableJavaFunction.
Let's say we want a function that generate a random number between 2 bounds.
The signature would be (int, int) -> int. So the function definition implementation can be :
import fr.jamailun.ultimatespellsystem.bukkit.runner.SpellRuntime;
import fr.jamailun.ultimatespellsystem.api.bukkit.runner.functions.RunnableJavaFunction;
import fr.jamailun.ultimatespellsystem.dsl.nodes.expressions.functions.FunctionArgument;
import fr.jamailun.ultimatespellsystem.dsl.nodes.expressions.functions.FunctionType;
import fr.jamailun.ultimatespellsystem.dsl.nodes.type.TypePrimitive;
import fr.jamailun.ultimatespellsystem.api.bukkit.providers.JavaFunctionProvider;
import org.jetbrains.annotations.NotNull;
public class RandBoundFunction extends RunnableJavaFunction {
private RandBoundFunction() {
super(
// Id of the function
"rand_in_bounds",
// Returned type
TypePrimitive.NUMBER.asType(),
// Expects two arguments: 'min' and 'max'. Both are not-optional.
List.of(
new FunctionArgument(FunctionType.acceptOnlyMono(TypePrimitive.NUMBER), "min", false),
new FunctionArgument(FunctionType.acceptOnlyMono(TypePrimitive.NUMBER), "max", false)
)
);
}
@Override
public Double compute(@NotNull List<RuntimeExpression> arguments, @NotNull SpellRuntime runtime) {
// because of the DSL declaration, we know arguments 1 and 2 exist and are numbers.
double min = runtime.safeEvaluate(arguments.get(0), Double.class);
double max = runtime.safeEvaluate(arguments.get(1), Double.class);
// return random number
return new Random().nextDouble(max - min) + min;
}
// And here, how's to register it
public static void registerElement() {
// Specialized implementation of #register
JavaFunctionProvider.instance().registerFunction(new RandBoundFunction());
}
}The syntax has not been improved, but it can be changed later on.
Implement a UssEntityType. Then, register it using EntityTypeProvider#register.
This will be used when summoning a creature, for example.
Scopes are used to search entities (all <SCOPE> around <ENTITY> ...) and to define the aggroable entities of a summon.
To provide a custom scope, simply use a "named" entity predicate.
Here, we create a scope named "players_with_a" and "a_players" that can target any Bukkit Player whose name starts with the letter "a".
import fr.jamailun.ultimatespellsystem.api.bukkit.providers.ScopeProvider;
public static void someSetup() {
// We want to target all player whose name starts with the letter 'a'.
ScopeProvider.instance().register(
e -> e instanceof Player p && p.getName().startsWith("a"),
// Accepted names in the DSL :
"players_with_a", "a_players"
);
}The spells can be cast using a cost. Each cost must be defined in the Java API.
You need to implement a SpellCost class, and register it, wrapped in a SpellCostEntry.
This entry will required a method to produce a cost from a list of string. Indeed, each cost
can register data into the item its bound to. For instance, let's implement a "RandomCost" that has a
specific chance of succeeding (this seems to be a very bad design, don't do it :P)
import fr.jamailun.ultimatespellsystem.api.bind.SpellCost;
import fr.jamailun.ultimatespellsystem.api.entities.SpellEntity;
import fr.jamailun.ultimatespellsystem.api.UltimateSpellSystem;
public class RandomCost implements SpellCost {
private double chances = 0.5;
// This is the method used to deserialize the cost.
// But a static method can also produce the same result !
public RandomCost(@NotNull List<String> serialized) {
this.cost = Double.parseDouble(serialized.getFirst());
}
@Override
public boolean canPay(@NotNull SpellEntity caster) {
return new java.util.Random().nextDouble(1) <= chances;
}
@Override
public void pay(@NotNull SpellEntity caster) {
// We don't remove anything from the caster :)
}
@Override
public @NotNull List<Object> serialize() {
return List.of(chances);
}
@Override
public String toString() {
return "Random[" + chances + "]";
}
// Register this class
public static void registerCost() {
UltimateSpellSystem.getSpellCostRegistry().register(
SpellCostEntry.of(
"random", // ID of the cost : must be unique.
RandomCost.class, // Serialized class
RandomCost::new, // Method to deserialize the cost : here the constructor
SpellCostArgType.DOUBLE // Varargs of arguments (here the 'chance' field)
)
);
}
}As listed in the summons attributes page, a lot of attributes do exist. You can create your own.
Don't forget that, at any point, you can get the attribute-values (and map) of a summon.
Here, we create an attribute "is_fire" (and "fire") that put the newly created summon in fire.
import fr.jamailun.ultimatespellsystem.api.bukkit.providers.SummonPropertiesProvider;
public class CustomSummonPropertiesExtension extends SummonPropertiesProvider {
public CustomSummonPropertiesExtension() {
// this util method allow us to guarantee arguments will be the boolean we want.
SummonProperty fireProperty = super.createForEntity((entity, bool, runtime) -> {
entity.setVisualFire(bool);
}, LivingEntity.class, Boolean.class);
// then, we register it
SummonPropertiesProvider.instance().register(fireProperty, "is_fire", "fire");
}
}To create a new callback, you'll need to define two things:
- A DSL definition,
- A listen to trigger this definition.
Here's an example taken from the "projectile hit" callback.
The goal is to be able to do a callback like: callback %entity landed at %loc: {}.
- The
landedkeyword will be the reserved keyword for our specific callback. - The
at %locis the argument. Here, we want it to be able to store the hit location of the projectile.
The goal is to allow for a new callback type.
import fr.jamailun.ultimatespellsystem.api.entities.CallbackAction;
import fr.jamailun.ultimatespellsystem.dsl.nodes.type.TypePrimitive;
import fr.jamailun.ultimatespellsystem.dsl.objects.CallbackEvent;
import fr.jamailun.ultimatespellsystem.dsl.tokenization.TokenType;
import org.bukkit.Location;
public class RegistrationDemo {
public static void registerProjectileLand() {
// Create a new callback action.
CallbackAction<ProjectileHitEvent, Location> definition = new CallbackAction<>(
// Defines the expected keyword, expected argument keyword and argument type.
// Here, the callback type will be "landed".
// The argument will be identified with the "AT %var" element.
// The argument will be the location.
CallbackEvent.of("landed", TokenType.AT, TypePrimitive.LOCATION),
// The listened event
ProjectileHitEvent.class,
// The argument content to use. Generally, we extract the value from the event.
e -> e.getEntity().getLocation()
);
// And call the register
CallbackEventProvider.instance().registerCallback(definition);
}
}Here's an example to detect when a projectile lands.
import org.bukkit.event.Listener;
import fr.jamailun.ultimatespellsystem.api.UltimateSpellSystem;
import fr.jamailun.ultimatespellsystem.api.entities.SummonAttributes;
public class ProjectileLandCallbacks implements Listener {
@EventHandler
void onEvent(@NotNull ProjectileHitEvent event) {
// The projectile will be the return value of the ProjectileHitEvent#getentity() method.
SummonAttributes summon = UltimateSpellSystem.getSummonsManager().find(event.getEntity().getUniqueId());
if (summon != null) {
// If no callback exist, it won't do anything.
summon.applyCallback(event);
}
}
}From the 2.2.0, properties exist to forbid a summon from attacking a caster "allies" (or projectile damage, or spell damage, ...).
Thus, USS must know if two entities are related. Using the same paradigm as every other registry, a "client" plugin will register a "AlliesCheck".
It is simply a form of BiFunction. It accepts the spell-caster and the targeted bukkit entity, and return a result.
The output value accepts 3 values :
-
ALLIES: the caster and the target are allied. Thus, if the spell supports it, not damage / aggro will be applied. -
ENEMIES: the plugin knows for sure entities are not allies. -
IGNORE: the plugin does not know. The next check may know more.
Both ALLIES and ENEMIES will stop any further check if returned.
Let's say we have a plugin FooParty that handles parties. Party members should not have summons attack each others.
The method FooParty.getPartyOf(UUID) will supposedly return a Party, and Party#contains(UUID) will check if entities share the same party.
import fr.jamailun.ultimatespellsystem.api.entities.SpellEntity;
import fr.jamailun.ultimatespellsystem.api.providers.AlliesProvider;
import fr.jamailun.ultimatespellsystem.api.providers.AlliesProvider.AlliesResult;
import org.bukkit.entity.Entity;
/**
* We implement the thing to be registered
*/
public class FooPartyAlliesCheck implements AlliesProvider.AlliesCheck {
@Override
public @NotNull AlliesResult test(@NotNull SpellEntity spellEntity, @NotNull Entity entity) {
Party party = FooParty.getPartyOf(spellEntity.getUniqueId());
if(party == null) return AlliesResult.IGNORE; // Caster is not in any party. Thus, this check may not now about alliance.
// Simply check if the party contains the target !
return party.contains(entity.getUniqueId()) ? AlliesResult.ALLIES : AlliesResult.IGNORE;
}
// Register to the provider
public static void registerThis() {
AlliesProvider.instance().register(new FooPartyAlliesCheck(), "foo-party");
}
}