Skip to content

Commit

Permalink
Implement example PlayerDelayedCommandAPICommand
Browse files Browse the repository at this point in the history
  • Loading branch information
willkroboth committed Aug 17, 2023
1 parent dbbccf3 commit 0270eca
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.github.jorelali;

import io.github.jorelali.delayedapicommand.DelayedAPICommands;
import io.github.jorelali.delayhandler.DelayHandlerCommands;
import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {
@Override
public void onEnable() {
DelayHandlerCommands.registerCommands();
DelayedAPICommands.registerCommands();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.jorelali.delayedapicommand;

import java.util.concurrent.TimeUnit;

public class DelayedAPICommands {
public static void registerCommands() {
// PlayerDelayedCommandAPICommand has all the functions of a CommandAPICommand, but also adds delay to any
// executors given by the `executesPlayer` methods

// PerPlayerDelayedCommandAPICommand keeps track of the delay for each player that uses it
new PerPlayerDelayedCommandAPICommand("delayedAPICommandPerPlayer", 10, TimeUnit.SECONDS)
.executesPlayer(info -> {
info.sender().sendMessage("You ran delayedAPICommandPerPlayer");
})
.register();

// GlobalPlayerDelayedCommandAPICommand shares its delay for all players
new GlobalPlayerDelayedCommandAPICommand("delayedAPICommandGlobal", 10, TimeUnit.SECONDS)
.executesPlayer((player, args) -> {
player.sendMessage("You ran delayedAPICommandGlobal");
})
.register();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.github.jorelali.delayedapicommand;

import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import org.bukkit.entity.Player;

import java.util.concurrent.TimeUnit;

// This shares its delay for all players
public class GlobalPlayerDelayedCommandAPICommand extends PlayerDelayedCommandAPICommand {
// The next time when this command will be allowed to run
// The default time is 0, which is always in the past, so the command will always be run the first time
private long nextTime = 0;

public GlobalPlayerDelayedCommandAPICommand(String commandName, long time, TimeUnit timeUnit) {
super(commandName, time, timeUnit);
}

@Override
void throwExceptionIfCannotRun(Player player) throws WrapperCommandSyntaxException {
// We don't have to worry about this overflowing for about 290 million years
// https://stackoverflow.com/questions/2978452/when-will-system-currenttimemillis-overflow
// This code will reward your patience by letting you run the command without waiting
long currentTime = System.currentTimeMillis();

// If it isn't time to run the command yet, throw the exception
if(currentTime < nextTime) {
throw CommandAPI.failWithString(
"This command cannot be run for another "
+ getDurationString(nextTime - currentTime)
);
}

// If the command is run, set the next possible time
nextTime = currentTime + delay;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.github.jorelali.delayedapicommand;

import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import org.bukkit.entity.Player;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

// This keeps track of the delay for each player that uses it
public class PerPlayerDelayedCommandAPICommand extends PlayerDelayedCommandAPICommand {
// Use UUID here in case player leaves and rejoins server to get around delay
private final Map<UUID, Long> nextTimesPerPlayer = new HashMap<>();

public PerPlayerDelayedCommandAPICommand(String commandName, long time, TimeUnit timeUnit) {
super(commandName, time, timeUnit);
}

@Override
void throwExceptionIfCannotRun(Player player) throws WrapperCommandSyntaxException {
// Get the next time when this player is allowed to run the command
// The default time is 0, which is always in the past, so the command will always be run the first time
long nextTime = nextTimesPerPlayer.getOrDefault(player.getUniqueId(), 0L);
long currentTime = System.currentTimeMillis();

// If it isn't time to run the command yet, throw the exception
if(currentTime < nextTime) {
throw CommandAPI.failWithString(
"You must wait "
+ getDurationString(nextTime - currentTime)
+ " before running this command again"
);
}

// If the command is run, set the next possible time
nextTimesPerPlayer.put(player.getUniqueId(), currentTime + delay);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.github.jorelali.delayedapicommand;

import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.*;
import org.bukkit.entity.Player;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

// This class extends CommandAPICommand to implement delayed commands
public abstract class PlayerDelayedCommandAPICommand extends CommandAPICommand {
protected final long delay;

public PlayerDelayedCommandAPICommand(String commandName, long time, TimeUnit timeUnit) {
super(commandName);
// Delay is the value in milliseconds
delay = timeUnit.toMillis(time);
}

// This method is implemented by the child classes
// If the command is currently delayed for the player, an exception should be thrown
abstract void throwExceptionIfCannotRun(Player player) throws WrapperCommandSyntaxException;

// This helper method formats a millisecond duration into a String representing how long is left in the delay
static String getDurationString(long millis) {
Duration duration = Duration.ofMillis(millis);

long days = duration.toDays();
long hours = duration.toHours() % 24;
long minutes = duration.toMinutes() % 60;
long seconds = duration.getSeconds() % 60;

String durationString;
if(days != 0) durationString = days + ":" + hours + ":" + minutes + " days";
else if (hours != 0) durationString = hours + ":" + minutes + ":" + seconds + "hours";
else if (minutes != 0) durationString = minutes + ":" + seconds + "minutes";
else if (seconds != 1) durationString = seconds + " seconds";
else durationString = "1 second";

return durationString;
}

// Override the usual executes methods to replace the executor with a delayed executor
// They take one CommandAPI executor and uses it inside a new executor
// The new executor also uses the throwExceptionIfCannotRun method to stop
// command execution if the command's delay is currently in effect
@Override
public CommandAPICommand executesPlayer(PlayerExecutionInfo executor) {
super.executesPlayer(info -> {
throwExceptionIfCannotRun(info.sender());
executor.run(info);
});

return this;
}

@Override
public CommandAPICommand executesPlayer(PlayerCommandExecutor executor) {
super.executesPlayer((player, args) -> {
throwExceptionIfCannotRun(player);
executor.run(player, args);
});

return this;
}

@Override
public CommandAPICommand executesPlayer(PlayerResultingExecutionInfo executor) {
super.executesPlayer(info -> {
throwExceptionIfCannotRun(info.sender());
return executor.run(info);
});

return this;
}

@Override
public CommandAPICommand executesPlayer(PlayerResultingCommandExecutor executor) {
super.executesPlayer((player, args) -> {
throwExceptionIfCannotRun(player);
executor.run(player, args);
});

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@ public static void registerCommands() {
// PerPlayerDelayHandler keeps track of the delay for each player that uses it
PlayerDelayHandler perPlayerDelay = new PerPlayerDelayHandler(10, TimeUnit.SECONDS);

new CommandAPICommand("perPlayerDelay1")
new CommandAPICommand("delayHandlerPerPlayer1")
// PlayerDelayHandler works directly on the Executors of the method, so it goes inside the `executes` methods
// This delay only acts on this method
// This also works for the executes methods of CommandTree and ArgumentTree
.executesPlayer(perPlayerDelay.delayMethod(info -> info.sender().sendMessage("You ran perPlayerDelay1")))
.executesPlayer(perPlayerDelay.delayMethod(info -> info.sender().sendMessage("You ran delayHandlerPerPlayer1")))
.register();

new CommandAPICommand("perPlayerDelay2")
new CommandAPICommand("delayHandlerPerPlayer2")
// While the PlayerDelayHandler only works on one Executor at a time, it can be reused for multiple methods,
// making them to share a delay
.executesPlayer(perPlayerDelay.delayMethod(info -> info.sender().sendMessage("You ran perPlayerDelay2")))
.executesPlayer(perPlayerDelay.delayMethod(info -> info.sender().sendMessage("You ran delayHandlerPerPlayer2")))
.register();

// GlobalPlayerDelayHandler shares its delay for all players
PlayerDelayHandler globalDelay = new GlobalPlayerDelayHandler(10, TimeUnit.SECONDS);

new CommandAPICommand("globalDelay1")
// This new PlayerDelayHandler is independent from the first two commands, since it is a different object
.executesPlayer(globalDelay.delayMethod(info -> info.sender().sendMessage("You ran globalDelay1")))
new CommandAPICommand("delayHandlerGlobal1")
// This new PlayerDelayHandler is independent of the first two commands, since it is a different object
.executesPlayer(globalDelay.delayMethod(info -> info.sender().sendMessage("You ran delayHandlerGlobal1")))
.register();

new CommandAPICommand("globalDelay2")
.executesPlayer(globalDelay.delayMethod(info -> info.sender().sendMessage("You ran globalDelay2")))
new CommandAPICommand("delayHandlerGlobal2")
.executesPlayer(globalDelay.delayMethod(info -> info.sender().sendMessage("You ran delayHandlerGlobal2")))
.register();
}
}

0 comments on commit 0270eca

Please sign in to comment.