Skip to content

Adding expansions

Wundero edited this page Sep 5, 2017 · 17 revisions

There are two primary ways to add an expansion to PlaceholderAPI. Because PlaceholderAPI provides a service, PlaceholderService, it is fairly simple to add placeholders.

@Plugin(name = "Example", id = "example", version = "1.0", dependencies = {
		@Dependency(id = "placeholderapi", version = "[4.0,)") })
// @Listening -> Include this annotation if the class is not a plugin class but
// you want to register listeners.
@ConfigSerializable // Include this annotation if you want @Setting annotated fields filled by
					// PlaceholderAPI's config.
public class Example { // The class to load placeholders from, if using load or loadAll, MUST be public.

	PlaceholderService s; // Make sure you add PlaceholderAPI to your gradle/maven dependencies

	@Listener
	public void onStart(GameStartingServerEvent e) {
		// I use provideUnchecked but it is recommended to use provide instead, unless
		// you can guarantee the existence of the service
		s = Sponge.getServiceManager().provideUnchecked(PlaceholderService.class);
		// The load all method acquires every method that is annotated with @Placeholder
		// in the 'holder' class, the first object provided, and creates a builder for
		// it. This is particularly nice because you can stream the builders nicely and
		// it can handle numerous placeholders at once.
		s.loadAll(this, this).stream().map(builder -> { // This is one way of loading placeholders, you could also load
														// each one individually or create from other sources.
			switch (builder.getId()) {
			case "multi":
				return builder.tokens("a", "b", null).description("Parse the token for a player!");
			case "msg":
				return builder.description("Send a message!");
			case "random":
				return builder.description("Generate a random number, from 0 to the number provided"
						+ ", or if no number is given, either 0 or 1.");
			}
			return builder;
		}).map(builder -> builder.author("Wundero").version("1.0")).forEach(builder -> {
			try {
				builder.buildAndRegister(); // buildAndRegister takes care of everything you want for you, so this is
											// all that is needed.
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		});
		// If you do not load from an object, you must specify plugin, id and function
		// for it to work.

		// The generic types here are necessary, and the builder() method will throw an
		// exception if they are not provided. They are not required for the load and
		// loadAll methods since they are provided by the methods being loaded.

		// The builder provides numerous parsing method functions by default.
		try {
			// The generic types of the Source and the Observer (the first two types) must
			// be one of, or a subclass/subinterface of:
			// Locatable, MessageReceiver, DataHolder or Subject.
			// The return type can be anything.
			s.<CommandSource, MessageReceiver, Void>builder().id("function").plugin(this).author("Wundero")
					.description("Execute a function.").consumeDual((s, r) -> r.sendMessage(Text.of(s.getName())))
					.buildAndRegister();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	// Any method annotated with the @Placeholder annotation must also be public.

	// Since the class is annotated with ConfigSerializable AND we called the
	// load/loadAll method on it, this will be filled.
	@Setting
	private String internalMsg = "Hello!";

	@Placeholder(id = "random") // Id must be specified, however this annotation and id are the only two
								// requirements.
	public int rand(@Token @Nullable Integer range) { // If the token's class is not a String or Optional<String>, an
														// attempt will be made to parse the token. If the attempt
														// fails, the token will be null.
		// If the nullable annotation is not specified, the parameter is GUARANTEED to
		// be non-null. (The placeholder will simply not parse if the parameter is null)
		if (range == null || range < 1) {
			return (int) Math.round(Math.random());
		}
		return (int) Math.round((Math.random() * range));
	}

	// The return type can be anything, primitives, arrays, objects, void, you name
	// it.

	@Placeholder(id = "source") // using '_' or ' ' is not recommended and should be avoided.
	public String getName(@Source CommandSource source) {
		return source.getName();
	}

	@Placeholder(id = "constant") // Ids must be unique within their own relational or non-relational group.
	public String constant() { // No parameters need to be specified.
		return "Wahoo!";
	}

	@Placeholder(id = "msg")
	public void msg(@Source MessageReceiver src, @Nullable @Token String message) {
		if (message == null) {
			message = internalMsg;
		}
		message = message.replace("_", " ");
		src.sendMessage(Text.of(TextColors.AQUA, message));
	}

	@Placeholder(id = "msg")
	@Relational // This annotation means that it will use the rel_msg placeholder as the placeholder. No other 
	            // guarantees are made, however relational and non-relational placeholder ids are stored separately, so it is possible 
	            // to have msg as both a relational and a non-relational id.
	public void relMsg(@Source CommandSource src, @Observer @Nullable MessageReceiver sendTo,
			@Nullable @Token Text message) {
		if (sendTo == null) {
			sendTo = src;
		}
		if (message == null) {
			message = Text.of(TextColors.GREEN, src.getName() + " says hello!");
		}
		sendTo.sendMessage(message);
	}

	@Placeholder(id = "multi") // "Fix" means to make the string all lower case.
	public Object multi(@Source Player src, @Nullable @Token(fix = true) String token) {
		if (token == null) {
			return src.getName();
		}
		switch (token) {
		case "a":
			return src.getName() + " A";
		case "b":
			return src.firstPlayed();
		}
		return null;
	}

}

There are additional methods for adding extra functionality, such as reload listeners and separation of config/listener objects from the placeholder, provided by the builder as well. See the builder's documentation on each method to see if you need or want to use it.

Clone this wiki locally