Skip to content

Implementing RPKit APIs

Ren edited this page Apr 24, 2022 · 4 revisions

» Home » Development » Implementing RPKit APIs

Setup

If you wish to implement RPKit APIs, you must depend on the library containing the service interfaces you wish to implement. If you are making your own library, you must depend on core-bukkit, plus any libraries required for functionality you want to build upon.

The maven repository can be found at https://repo.rpkit.com/repository/maven-releases/ and browsed at https://repo.rpkit.com/#browse/browse:maven-releases.

Gradle Groovy DSL:

repositories {
  maven { url "https://repo.rpkit.com/repository/maven-releases/" }
}

dependencies {
  implementation group: "com.rpkit", name: "rpk-core-bukkit", version: "2.2.0"
  implementation group: "com.rpkit", name: "rpk-character-lib-bukkit", version: "2.2.0"
}

Gradle Kotlin DSL:

repositories {
  maven(url = "https://repo.rpkit.com/repository/maven-releases/")
}

dependencies {
  "implementation"(group = "com.rpkit", name = "rpk-core-bukkit", version = "2.2.0")
  "implementation"(group = "com.rpkit", name = "rpk-character-lib-bukkit", version = "2.2.0")
}

Maven:

<repositories>
  <repository>
    <id>rpkit-repo</id>
    <url>https://repo.rpkit.com/repository/maven-releases/</url>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupId>com.rpkit</groupId>
    <artifactId>rpk-core-bukkit</artifactId>
    <version>2.2.0</version>
  </dependency>
  <dependency>
    <groupId>com.rpkit</groupId>
    <artifactId>rpk-character-lib-bukkit</artifactId>
    <version>2.2.0</version>
  </dependency>
</dependencies>

You can extend RPKBukkitPlugin (as opposed to JavaPlugin) for some convenience methods but this is not required since 2.0.0.

public class MyPlugin extends RPKBukkitPlugin {
}

Services

You can register services using the Services singleton. This will register it with the Bukkit services provider, and also notify any plugins waiting on the service's existence that it is ready.

@Override
public void onEnable() {
  Services.INSTANCE.set(RPKCharacterService.class, new MyCharacterService());
}

Services must implement a service interface - these inherit from Service and may be found in various libs. For example if you're making a custom character plugin, you can use rpk-character-lib's RPKCharacterService interface, and any plugins which use characters will automatically support it.

public final class MyCharacterService implements RPKCharacterService {

  private final MyPlugin plugin;

  public MyCharacterProvider(MyPlugin plugin) {
    this.plugin = plugin;
  }

  @Override
  public RPKCharacter getCharacter(int id) {
    return new MyCharacter(id);
  }
  @Override
  public RPKCharacter getActiveCharacter(RPKMinecraftProfile minecraftProfile) {
    return plugin.getDatabase().getTable(MyCharacterTable.class).get(minecraftProfile);
  }
  // etc...
}

Database

RPKit modules tend to use Flyway to manage migrations and jOOQ's typesafe query builders.

If you are writing your own plugin you can follow this approach or you can use your own.

RPKit offers some convenience classes for interacting with the database. Database encapsulates connection pooling, migrations and table retrieval. Table represents a database table and may be retrieved from a Database instance. Each plugin may set up & use their own Database, or handle their own persistence in another manner.

Messages

RPKit provides a BukkitMessages class for retrieving messages from the messages.yml file in a typesafe way. This allows servers running in other languages to translate all text within a plugin, or for servers to tweak wording and styling to give their server a custom "feel".

Default messages should be set in a messages.yml file in the plugin's resources. Upon initialisation, BukkitMessages will save this automatically.

Your extended messages class can then state which messages it expects, e.g.:

public final class MyMessages extends BukkitMessages {
    public String getMySimpleMessage() {
        return get("my-simple-message");
    }
    public static class MyComplexMessage {
        private ParameterizedMessage message;
        public MyComplexMessage(ParameterizedMessage message) {
            this.message = message;
        }
        public String withParameters(String myParameterOne, String myParameterTwo) {
            return message.withParameters(new HashMap<>() {{
                put("my_parameter_one", myParameterOne);
                put("my_parameter_two", myParameterTwo);
            }});
        }
    }
    public MyComplexMessage getMyComplexMessage() {
        return new MyComplexMessage(getParameterized("my-complex-message"));
    }
}

Web

Since RPKit 2.x, RPKit plugins may provide their own web API, but there is no centrally-handled way of doing so any more. The previous web UI has been removed and we encourage servers to build their own websites around the provided web APIs.

Commands and Listeners

RPKBukkitPlugin provides a convenience method to register multiple listeners in a shorter manner:

@Override
public void onEnable() {
    registerListeners(
        new MyListener1(),
        new MyListener2()
    );
}

For commands, RPKit provides a server vendor-agnostic command class with typesafe results that can then be converted to work with a specific vendor. Commands will be launched in the main thread, but can perform asynchronous behaviour using CompletableFuture and return their result after completion without impacting the main thread. Commands that run entirely synchronously need only return CompletableFuture.completedFuture().

public class MyCommand implements RPKCommandExecutor {
    @Override
    public CompletableFuture<CommandResult> onCommand(RPKCommandSender sender, String[] args) {
        sender.sendMessage("Hi!");
        return CompletableFuture.completedFuture(CommandSuccess.INSTANCE);
    }
}

public class MyPlugin extends RPKBukkitPlugin {
    @Override
    public void onEnable() {
        getCommand("mycommand").setExecutor(RPKBukkitCommandExecutorKt.toBukkit(new MyCommand()));
    }
}

Custom character card fields

With rpk-character-lib, you may provide custom character card fields that may be included in the character card template. For example, rpk-economy provides a money field that allows you to show a character's money on their character card.

Custom character card fields may either implement CharacterCardField or HideableCharacterCardField, HideableCharacterCardFields may be hidden by the player.

public class MoneyField implements HideableCharacterCardField {

  private final RPKEconomyBukkit plugin;

  public MoneyField(RPKEconomyBukkit plugin) {
    this.plugin = plugin;
  }

  @Override
  public String getName() {
    return "money";
  }

  @Override
  public String get(RPKCharacter character) {
    if (isHidden(character)) {
      return "[HIDDEN]";
    } else {
      RPKEconomyProvider economyProvider = plugin.getCore().getServiceManager().getServiceProvider(RPKEconomyProvider.class);
      RPKCurrencyProvider currencyProvider = plugin.getCore().getServiceManager().getServiceProvider(RPKCurrencyProvider.class);
      return currencyProvider.getCurrencies()
              .stream()
              .map(currency -> {
                int balance = economyProvider.getBalance(character, currency);
                return balance + balance == 1 ? currency.getNameSingular() : currency.getNamePlural();
              })
              .collect(Collectors.joining(", "));
  }

  @Override
  public boolean isHidden(RPKCharacter character) {
    return plugin.getDatabase().getTable(MoneyHiddenTable.class).get(character) != null
  }

  @Override
  public void setHidden(RPKCharacter character, boolean hidden) {
    MoneyHiddenTable table = plugin.getDatabase().getTable(MoneyHiddenTable.class);
    if (hidden) {
      if (table.get(character) == null) {
        table.insert(new MoneyHidden(0, character);
      }
    } else {
      MoneyHidden moneyHidden = table.get(character);
      if (moneyHidden != null) {
        table.delete(moneyHidden);
      }
    }
  }

}

Character card field registration may be performed as soon as the service becomes available by using Services:

Services.INSTANCE.require(RPKCharacterCardFieldService.class)
    .whenAvailable(service -> service.getCharacterCardFields().add(new MoneyField()));

Custom stat variables

Following a similar pattern to the above, but with rpk-stat-lib, you can make implementations of RPKStatVariable, and register them with RPKStatVariableService. These may then be used to calculate stats in the stat plugin.

Skills

Again, following the same pattern, you can use rpk-skill-lib, implement RPKSkill, and register them with with RPKSkillProvider.

Clone this wiki locally