Skip to content

Fluent API for gameserver schedulers targeting Minecraft server software like Spigot/Paper

License

Notifications You must be signed in to change notification settings

OkaeriPoland/okaeri-tasker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Okaeri Tasker

License Total lines Repo size Contributors Discord

Fluent API for gameserver schedulers targeting Minecraft server software like Spigot/Paper. Part of the okaeri-platform.

Installation

Maven

Add repository to the repositories section:

<repository>
    <id>okaeri-repo</id>
    <url>https://storehouse.okaeri.eu/repository/maven-public/</url>
</repository>

Add dependency to the dependencies section:

<!-- for bukkit (default async, sync) -->
<dependency>
  <groupId>eu.okaeri</groupId>
  <artifactId>okaeri-tasker-bukkit</artifactId>
  <version>3.0.2-beta.5</version>
</dependency>
<!-- for velocity (default async) -->
<dependency>
  <groupId>eu.okaeri</groupId>
  <artifactId>okaeri-tasker-velocity</artifactId>
  <version>3.0.2-beta.5</version>
</dependency>
<!-- for bungee (default async) -->
<dependency>
  <groupId>eu.okaeri</groupId>
  <artifactId>okaeri-tasker-bungee</artifactId>
  <version>3.0.2-beta.5</version>
</dependency>
<!-- for folia and paper 1.20+ (default async, entity, global, location) -->
<dependency>
  <groupId>eu.okaeri</groupId>
  <artifactId>okaeri-tasker-folia</artifactId>
  <version>3.0.2-beta.5</version>
</dependency>

Example usage

See TaskerTest and visit okaeri-platform-bukkit's example here or below.

// create instance, preferably one per plugin
// BukkitTasker type is required to access .xSync methods
BukkitTasker tasker = BukkitTasker.newPool(pluginInstance);

public void handleEvent(SomePlayerEvent event) {
    // standard access, create new chain
    this.tasker.newChain()
        // get data from service into chain stack asynchronously
        .supply(() -> this.playerPersistence.get(event.getPlayer()))
        // manipulate the data synchronously
        .acceptSync(playerProperties -> {
            Instant lastJoined = playerProperties.getLastJoined();
            event.getPlayer().sendMessage("Your last join time: " + lastJoined);
            playerProperties.setLastJoined(Instant.now());
            playerProperties.setLastJoinedLocation(event.getPlayer().getLocation());
        })
        // asynchronously save the data - you may notice
        // that previous method does not need to return value
        // because stack data persists between chain parts
        .accept(playerProperties::save)
        .execute();
}

Real-life uses

Various snippets directly from the internal okaeri codebase. May and will include code interacting with other libraries like okaeri-i18n or references to other internal code. All of these were built with okaeri-platform.

General flow control

Useful when implementing commands interacting with APIs that cannot be accessed safely asynchronously.

import static eu.okaeri.tasker.core.TaskerDsl.*;

// class marked with @Async

@Executor
public TaskerChain<?> __(@Context Player sender, @Chain("hat") BukkitTaskerChain<?> chain) {
    return chain
        .abortIfThen(
            this.tasker.sync(cond(() -> sender.getInventory().getItemInMainHand().getType().isAir())), // sync
            run(() -> this.i18n.get(this.messages.getHatError()).sendTo(sender)) // async
        )
        .runSync(() -> { // sync
            PlayerInventory inventory = sender.getInventory();
            ItemStack itemInHand = inventory.getItemInMainHand();
            inventory.setItemInMainHand(inventory.getHelmet());
            inventory.setHelmet(itemInHand);
        })
        .run(() -> this.i18n.get(this.messages.getHatSuccess()).sendTo(sender)); // async
}

Async book processing

Allows book filtering in PlayerEditBookEvent using OK! AI.Censor to take place without doing blocking I/O in the main thread. Can also be applied in similar fashion to sign editing.

import static eu.okaeri.tasker.core.TaskerDsl.*;

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void handleEditBook(PlayerEditBookEvent event) {

    Player player = event.getPlayer();
    ItemStack book = (event.getSlot() == -1)
        ? player.getInventory().getItemInOffHand()
        : player.getInventory().getItem(event.getSlot());

    if ((book == null) || (book.getItemMeta() == null)) {
        return;
    }

    this.tasker.newChain()
        .supply(() -> {
            String contents = this.bookToString(event.getNewBookMeta());
            return new AsyncPlayerTextEvent(player, contents, "Book").call();
        })
        .abortIf(not(AsyncPlayerTextEvent::isCancelled))
        .runSync(() -> {
            BookMeta bookMeta = (BookMeta) book.getItemMeta();
            bookMeta.setPages(event.getPreviousBookMeta().getPages());
            book.setItemMeta(bookMeta);
            book.setType(Material.WRITABLE_BOOK);
        })
        .execute();
}

Delayed teleport

Delayed teleportation for warps and similar. Checks if distance between starting and current location is below 1. Executes action when there was no rejected state for the delayed time.

Duration teleportDuration = this.spawnConfig.getTeleportDuration();
Location startingLocation = player.getLocation().clone();

this.tasker.newDelayer(teleportDuration, Duration.ofSeconds(1))
    .abortIfNot(player::isOnline)
    .abortIfThen(
        () -> startingLocation.distanceSquared(player.getLocation()) > 0.25, // sqrt(1)=0.25
        () -> this.i18n.get(this.messages.getTeleportationCancelledError()).sendTo(player)
    )
    .delayed(() -> {
        this.module.teleportToSpawn(player, true);
        this.i18n.get(this.messages.getTeleportationSuccess()).sendTo(player);
    })
    .execute(this.tasker.getAsync());

Detecting hook landing

Wait-loop for detecting hook landing in the PlayerFishEvent for state FISHING. Executes code on condition only using #forced or timeouts with no action. Note that using #delayed would allow execution on force or after timeout.

this.tasker.newDelayer(Duration.ofSeconds(10))
    .abortIf(hook::isDead)
    .abortIfNot(player::isOnline)
    .forceIf(hook::isInWater)
    .forced(() -> {
        this.i18n.get(this.messages.getFishingStateInfo())
            .with("chance", (1d / (double) fishesCaughtAtHook) * 100d)
            .target(BukkitMessageTarget.ACTION_BAR)
            .sendTo(player);
    })
    .execute(this.tasker.getSync());

About

Fluent API for gameserver schedulers targeting Minecraft server software like Spigot/Paper

Topics

Resources

License

Stars

Watchers

Forks

Languages