Expose rollbacks to the API, refactor the scheduler

This commit is contained in:
2026-05-19 13:22:38 -04:00
parent e1a6b57f22
commit 8a6c695e10
21 changed files with 586 additions and 250 deletions
@@ -9,6 +9,7 @@ import dev.plex.api.message.MessageApi;
import dev.plex.api.module.ModulesApi;
import dev.plex.api.player.PlayersApi;
import dev.plex.api.punishment.PunishmentsApi;
import dev.plex.api.rollback.RollbackApi;
import dev.plex.api.scheduler.SchedulerApi;
import dev.plex.api.storage.StorageApi;
@@ -49,6 +50,8 @@ public interface PlexApi
PunishmentsApi punishments();
RollbackApi rollback();
SchedulerApi scheduler();
StorageApi storage();
@@ -0,0 +1,15 @@
package dev.plex.api.rollback;
import org.bukkit.command.CommandSender;
public interface RollbackApi
{
boolean isAvailable();
boolean rollback(CommandSender sender, String playerName, int seconds);
default boolean rollbackLastDay(CommandSender sender, String playerName)
{
return rollback(sender, playerName, 86400);
}
}
@@ -1,8 +1,142 @@
package dev.plex.api.scheduler;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.Nullable;
public interface SchedulerApi
{
Object runSync(Runnable task);
Object runLater(Runnable task, long delayTicks);
Object runTimer(Runnable task, long delayTicks, long periodTicks);
Executor asyncExecutor();
void executeGlobal(Runnable task);
ScheduledTask runGlobal(Consumer<ScheduledTask> task);
default ScheduledTask runGlobal(Runnable task)
{
return runGlobal(scheduledTask -> task.run());
}
ScheduledTask runGlobalLater(Consumer<ScheduledTask> task, long delayTicks);
default ScheduledTask runGlobalLater(Runnable task, long delayTicks)
{
return runGlobalLater(scheduledTask -> task.run(), delayTicks);
}
ScheduledTask runGlobalTimer(Consumer<ScheduledTask> task, long delayTicks, long periodTicks);
default ScheduledTask runGlobalTimer(Runnable task, long delayTicks, long periodTicks)
{
return runGlobalTimer(scheduledTask -> task.run(), delayTicks, periodTicks);
}
ScheduledTask runAsync(Consumer<ScheduledTask> task);
default ScheduledTask runAsync(Runnable task)
{
return runAsync(scheduledTask -> task.run());
}
ScheduledTask runAsyncLater(Consumer<ScheduledTask> task, long delay, TimeUnit unit);
default ScheduledTask runAsyncLater(Runnable task, long delay, TimeUnit unit)
{
return runAsyncLater(scheduledTask -> task.run(), delay, unit);
}
ScheduledTask runAsyncTimer(Consumer<ScheduledTask> task, long delay, long period, TimeUnit unit);
default ScheduledTask runAsyncTimer(Runnable task, long delay, long period, TimeUnit unit)
{
return runAsyncTimer(scheduledTask -> task.run(), delay, period, unit);
}
void executeRegion(Location location, Runnable task);
void executeRegion(World world, int chunkX, int chunkZ, Runnable task);
ScheduledTask runRegion(Location location, Consumer<ScheduledTask> task);
default ScheduledTask runRegion(Location location, Runnable task)
{
return runRegion(location, scheduledTask -> task.run());
}
ScheduledTask runRegion(World world, int chunkX, int chunkZ, Consumer<ScheduledTask> task);
default ScheduledTask runRegion(World world, int chunkX, int chunkZ, Runnable task)
{
return runRegion(world, chunkX, chunkZ, scheduledTask -> task.run());
}
ScheduledTask runRegionLater(Location location, Consumer<ScheduledTask> task, long delayTicks);
default ScheduledTask runRegionLater(Location location, Runnable task, long delayTicks)
{
return runRegionLater(location, scheduledTask -> task.run(), delayTicks);
}
ScheduledTask runRegionLater(World world, int chunkX, int chunkZ, Consumer<ScheduledTask> task, long delayTicks);
default ScheduledTask runRegionLater(World world, int chunkX, int chunkZ, Runnable task, long delayTicks)
{
return runRegionLater(world, chunkX, chunkZ, scheduledTask -> task.run(), delayTicks);
}
ScheduledTask runRegionTimer(Location location, Consumer<ScheduledTask> task, long delayTicks, long periodTicks);
default ScheduledTask runRegionTimer(Location location, Runnable task, long delayTicks, long periodTicks)
{
return runRegionTimer(location, scheduledTask -> task.run(), delayTicks, periodTicks);
}
ScheduledTask runRegionTimer(World world, int chunkX, int chunkZ, Consumer<ScheduledTask> task, long delayTicks, long periodTicks);
default ScheduledTask runRegionTimer(World world, int chunkX, int chunkZ, Runnable task, long delayTicks, long periodTicks)
{
return runRegionTimer(world, chunkX, chunkZ, scheduledTask -> task.run(), delayTicks, periodTicks);
}
boolean executeEntity(Entity entity, Runnable task, @Nullable Runnable retired, long delayTicks);
default boolean executeEntity(Entity entity, Runnable task, long delayTicks)
{
return executeEntity(entity, task, null, delayTicks);
}
@Nullable
ScheduledTask runEntity(Entity entity, Consumer<ScheduledTask> task, @Nullable Runnable retired);
default @Nullable ScheduledTask runEntity(Entity entity, Runnable task)
{
return runEntity(entity, scheduledTask -> task.run(), null);
}
@Nullable
ScheduledTask runEntityLater(Entity entity, Consumer<ScheduledTask> task, @Nullable Runnable retired, long delayTicks);
default @Nullable ScheduledTask runEntityLater(Entity entity, Runnable task, long delayTicks)
{
return runEntityLater(entity, scheduledTask -> task.run(), null, delayTicks);
}
@Nullable
ScheduledTask runEntityTimer(Entity entity, Consumer<ScheduledTask> task, @Nullable Runnable retired, long delayTicks, long periodTicks);
default @Nullable ScheduledTask runEntityTimer(Entity entity, Runnable task, long delayTicks, long periodTicks)
{
return runEntityTimer(entity, scheduledTask -> task.run(), null, delayTicks, periodTicks);
}
void cancelGlobalTasks();
void cancelAsyncTasks();
}
@@ -12,6 +12,7 @@ import dev.plex.api.message.MessageApi;
import dev.plex.api.module.ModulesApi;
import dev.plex.api.player.PlayersApi;
import dev.plex.api.punishment.PunishmentsApi;
import dev.plex.api.rollback.RollbackApi;
import dev.plex.api.scheduler.SchedulerApi;
import dev.plex.api.storage.StorageApi;
@@ -38,6 +39,7 @@ public final class DefaultPlexApi implements PlexApi
@Override public MessageApi messages() { throw unsupported(); }
@Override public PlayersApi players() { throw unsupported(); }
@Override public PunishmentsApi punishments() { throw unsupported(); }
@Override public RollbackApi rollback() { throw unsupported(); }
@Override public SchedulerApi scheduler() { throw unsupported(); }
@Override public StorageApi storage() { throw unsupported(); }
+11 -4
View File
@@ -11,6 +11,7 @@ import dev.plex.handlers.CommandHandler;
import dev.plex.handlers.ListenerHandler;
import dev.plex.hook.CoreProtectHook;
import dev.plex.hook.PrismHook;
import dev.plex.hook.RollbackManager;
import dev.plex.module.ModuleManager;
import dev.plex.player.PlayerService;
import dev.plex.player.PlexPlayer;
@@ -18,7 +19,6 @@ import dev.plex.punishment.PunishmentManager;
import dev.plex.services.ServiceManager;
import dev.plex.storage.RedisConnection;
import dev.plex.storage.SQLConnection;
import dev.plex.storage.StorageExecutor;
import dev.plex.storage.StorageType;
import dev.plex.storage.player.SQLPlayerData;
import dev.plex.storage.punishment.SQLNotes;
@@ -79,6 +79,7 @@ public class Plex extends JavaPlugin
private CoreProtectHook coreProtectHook;
private PrismHook prismHook;
private RollbackManager rollbackManager;
public static Plex get()
{
@@ -227,6 +228,8 @@ public class Plex extends JavaPlugin
PlexLog.debug("Not hooking into SuperVanish / PremiumVanish");
}
rollbackManager = new RollbackManager(this);
updateChecker = new UpdateChecker(this);
PlexLog.log("Update checking enabled");
@@ -246,9 +249,9 @@ public class Plex extends JavaPlugin
PlexLog.log("Redis is disabled in the configuration file, not connecting.");
}
punishmentRepository = new SQLPunishment(sqlConnection.getConnectionSource());
punishmentRepository = new SQLPunishment(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
playerRepository = new SQLPlayerData(sqlConnection.getConnectionSource(), punishmentRepository);
noteRepository = new SQLNotes(sqlConnection.getConnectionSource());
noteRepository = new SQLNotes(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
playerService = new PlayerService(playerCache, playerRepository);
new ListenerHandler(this);
@@ -292,13 +295,17 @@ public class Plex extends JavaPlugin
this.getServer().getMessenger().unregisterOutgoingPluginChannel(this);
if (serviceManager != null)
{
serviceManager.endServices();
}
moduleManager.disableModules();
if (sqlConnection != null)
{
sqlConnection.close();
}
StorageExecutor.shutdown();
}
private void generateWorlds()
@@ -12,6 +12,7 @@ import dev.plex.api.message.MessageApi;
import dev.plex.api.module.ModulesApi;
import dev.plex.api.player.PlayersApi;
import dev.plex.api.punishment.PunishmentsApi;
import dev.plex.api.rollback.RollbackApi;
import dev.plex.api.scheduler.SchedulerApi;
import dev.plex.api.storage.StorageApi;
@@ -27,6 +28,7 @@ public final class DefaultPlexApi implements PlexApi
private final MessageApi messages;
private final PlayersApi players;
private final PunishmentsApi punishments;
private final RollbackApi rollback;
private final SchedulerApi scheduler;
private final StorageApi storage;
@@ -42,6 +44,7 @@ public final class DefaultPlexApi implements PlexApi
this.messages = new DefaultMessageApi();
this.players = new DefaultPlayersApi(plugin);
this.punishments = new DefaultPunishmentsApi(plugin);
this.rollback = new DefaultRollbackApi(plugin);
this.scheduler = new DefaultSchedulerApi(plugin);
this.storage = new DefaultStorageApi(plugin);
}
@@ -56,6 +59,7 @@ public final class DefaultPlexApi implements PlexApi
@Override public MessageApi messages() { return messages; }
@Override public PlayersApi players() { return players; }
@Override public PunishmentsApi punishments() { return punishments; }
@Override public RollbackApi rollback() { return rollback; }
@Override public SchedulerApi scheduler() { return scheduler; }
@Override public StorageApi storage() { return storage; }
}
@@ -0,0 +1,30 @@
package dev.plex.api.impl;
import dev.plex.Plex;
import dev.plex.api.rollback.RollbackApi;
import dev.plex.hook.RollbackManager;
import org.bukkit.command.CommandSender;
final class DefaultRollbackApi implements RollbackApi
{
private final Plex plugin;
DefaultRollbackApi(Plex plugin)
{
this.plugin = plugin;
}
@Override
public boolean isAvailable()
{
RollbackManager rollbackManager = plugin.getRollbackManager();
return rollbackManager != null && rollbackManager.isAvailable();
}
@Override
public boolean rollback(CommandSender sender, String playerName, int seconds)
{
RollbackManager rollbackManager = plugin.getRollbackManager();
return rollbackManager != null && rollbackManager.rollback(sender, playerName, seconds);
}
}
@@ -2,14 +2,158 @@ package dev.plex.api.impl;
import dev.plex.Plex;
import dev.plex.api.scheduler.SchedulerApi;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.Nullable;
final class DefaultSchedulerApi implements SchedulerApi
{
private final Plex plugin;
private final Executor asyncExecutor;
DefaultSchedulerApi(Plex plugin) { this.plugin = plugin; }
DefaultSchedulerApi(Plex plugin)
{
this.plugin = plugin;
this.asyncExecutor = task -> Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
}
@Override public Object runSync(Runnable task) { return plugin.getServer().getScheduler().runTask(plugin, task); }
@Override public Object runLater(Runnable task, long delayTicks) { return plugin.getServer().getScheduler().runTaskLater(plugin, task, delayTicks); }
@Override public Object runTimer(Runnable task, long delayTicks, long periodTicks) { return plugin.getServer().getScheduler().runTaskTimer(plugin, task, delayTicks, periodTicks); }
@Override
public Executor asyncExecutor()
{
return asyncExecutor;
}
@Override
public void executeGlobal(Runnable task)
{
Bukkit.getGlobalRegionScheduler().execute(plugin, task);
}
@Override
public ScheduledTask runGlobal(Consumer<ScheduledTask> task)
{
return Bukkit.getGlobalRegionScheduler().run(plugin, task);
}
@Override
public ScheduledTask runGlobalLater(Consumer<ScheduledTask> task, long delayTicks)
{
return Bukkit.getGlobalRegionScheduler().runDelayed(plugin, task, delayTicks);
}
@Override
public ScheduledTask runGlobalTimer(Consumer<ScheduledTask> task, long delayTicks, long periodTicks)
{
return Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, task, delayTicks, periodTicks);
}
@Override
public ScheduledTask runAsync(Consumer<ScheduledTask> task)
{
return Bukkit.getAsyncScheduler().runNow(plugin, task);
}
@Override
public ScheduledTask runAsyncLater(Consumer<ScheduledTask> task, long delay, TimeUnit unit)
{
return Bukkit.getAsyncScheduler().runDelayed(plugin, task, delay, unit);
}
@Override
public ScheduledTask runAsyncTimer(Consumer<ScheduledTask> task, long delay, long period, TimeUnit unit)
{
return Bukkit.getAsyncScheduler().runAtFixedRate(plugin, task, delay, period, unit);
}
@Override
public void executeRegion(Location location, Runnable task)
{
Bukkit.getRegionScheduler().execute(plugin, location, task);
}
@Override
public void executeRegion(World world, int chunkX, int chunkZ, Runnable task)
{
Bukkit.getRegionScheduler().execute(plugin, world, chunkX, chunkZ, task);
}
@Override
public ScheduledTask runRegion(Location location, Consumer<ScheduledTask> task)
{
return Bukkit.getRegionScheduler().run(plugin, location, task);
}
@Override
public ScheduledTask runRegion(World world, int chunkX, int chunkZ, Consumer<ScheduledTask> task)
{
return Bukkit.getRegionScheduler().run(plugin, world, chunkX, chunkZ, task);
}
@Override
public ScheduledTask runRegionLater(Location location, Consumer<ScheduledTask> task, long delayTicks)
{
return Bukkit.getRegionScheduler().runDelayed(plugin, location, task, delayTicks);
}
@Override
public ScheduledTask runRegionLater(World world, int chunkX, int chunkZ, Consumer<ScheduledTask> task, long delayTicks)
{
return Bukkit.getRegionScheduler().runDelayed(plugin, world, chunkX, chunkZ, task, delayTicks);
}
@Override
public ScheduledTask runRegionTimer(Location location, Consumer<ScheduledTask> task, long delayTicks, long periodTicks)
{
return Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, task, delayTicks, periodTicks);
}
@Override
public ScheduledTask runRegionTimer(World world, int chunkX, int chunkZ, Consumer<ScheduledTask> task, long delayTicks, long periodTicks)
{
return Bukkit.getRegionScheduler().runAtFixedRate(plugin, world, chunkX, chunkZ, task, delayTicks, periodTicks);
}
@Override
public boolean executeEntity(Entity entity, Runnable task, @Nullable Runnable retired, long delayTicks)
{
return entity.getScheduler().execute(plugin, task, retired, delayTicks);
}
@Override
public @Nullable ScheduledTask runEntity(Entity entity, Consumer<ScheduledTask> task, @Nullable Runnable retired)
{
return entity.getScheduler().run(plugin, task, retired);
}
@Override
public @Nullable ScheduledTask runEntityLater(Entity entity, Consumer<ScheduledTask> task, @Nullable Runnable retired, long delayTicks)
{
return entity.getScheduler().runDelayed(plugin, task, retired, delayTicks);
}
@Override
public @Nullable ScheduledTask runEntityTimer(Entity entity, Consumer<ScheduledTask> task, @Nullable Runnable retired, long delayTicks, long periodTicks)
{
return entity.getScheduler().runAtFixedRate(plugin, task, retired, delayTicks, periodTicks);
}
@Override
public void cancelGlobalTasks()
{
Bukkit.getGlobalRegionScheduler().cancelTasks(plugin);
}
@Override
public void cancelAsyncTasks()
{
Bukkit.getAsyncScheduler().cancelTasks(plugin);
}
}
@@ -14,10 +14,8 @@ import dev.plex.util.PlexLog;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -28,9 +26,6 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.prism_mc.prism.api.activities.ActivityQuery;
import org.prism_mc.prism.paper.api.PrismPaperApi;
import org.prism_mc.prism.paper.api.activities.PaperActivityQuery;
@CommandParameters(name = "ban", usage = "/<command> <player> [reason] [-rb]", aliases = "offlineban,gtfo", description = "Bans a player, offline or online")
@CommandPermissions(permission = "plex.ban", source = RequiredCommandSource.ANY)
@@ -56,84 +51,52 @@ public class BanCMD extends ServerCommand
plugin.getPunishmentManager().isAsyncBanned(plexPlayer.getUuid()).whenComplete((aBoolean, throwable) ->
{
if (aBoolean)
{
send(sender, messageComponent("playerBanned"));
return;
}
String reason;
Punishment punishment = new Punishment(plexPlayer.getUuid(), getUUID(sender));
punishment.setType(PunishmentType.BAN);
boolean rollBack = false;
if (args.length > 1)
{
reason = StringUtils.join(args, " ", 1, args.length);
String newReason = StringUtils.normalizeSpace(reason.replace("-rb", ""));
punishment.setReason(newReason.trim().isEmpty() ? messageString("noReasonProvided") : newReason);
rollBack = reason.startsWith("-rb") || reason.endsWith("-rb");
}
else
{
punishment.setReason(messageString("noReasonProvided"));
}
punishment.setPunishedUsername(plexPlayer.getName());
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
punishment.setEndDate(date.plusDays(1));
punishment.setCustomTime(false);
punishment.setActive(true);
punishment.setIp(player != null ? player.getAddress().getAddress().getHostAddress().trim() : plexPlayer.getIps().getLast());
plugin.getPunishmentManager().punish(plexPlayer, punishment);
PlexUtils.broadcast(messageComponent("banningPlayer", sender.getName(), plexPlayer.getName()));
Bukkit.getGlobalRegionScheduler().execute(plugin, () ->
plugin.getApi().scheduler().runGlobal(() ->
{
if (throwable != null)
{
PlexLog.error("Unable to check ban state for {0}: {1}", plexPlayer.getName(), throwable.getMessage());
return;
}
if (aBoolean)
{
send(sender, messageComponent("playerBanned"));
return;
}
String reason;
Punishment punishment = new Punishment(plexPlayer.getUuid(), getUUID(sender));
punishment.setType(PunishmentType.BAN);
boolean rollBack = false;
if (args.length > 1)
{
reason = StringUtils.join(args, " ", 1, args.length);
String newReason = StringUtils.normalizeSpace(reason.replace("-rb", ""));
punishment.setReason(newReason.trim().isEmpty() ? messageString("noReasonProvided") : newReason);
rollBack = reason.startsWith("-rb") || reason.endsWith("-rb");
}
else
{
punishment.setReason(messageString("noReasonProvided"));
}
punishment.setPunishedUsername(plexPlayer.getName());
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
punishment.setEndDate(date.plusDays(1));
punishment.setCustomTime(false);
punishment.setActive(true);
punishment.setIp(plexPlayer.getIps().getLast());
plugin.getPunishmentManager().punish(plexPlayer, punishment);
PlexUtils.broadcast(messageComponent("banningPlayer", sender.getName(), plexPlayer.getName()));
if (player != null)
{
BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService()));
plugin.getApi().scheduler().runEntity(player, () -> BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService())));
}
PlexLog.debug("(From /ban command) PunishedPlayer UUID: " + plexPlayer.getUuid());
if (rollBack)
{
plugin.getApi().rollback().rollbackLastDay(sender, plexPlayer.getName());
}
});
PlexLog.debug("(From /ban command) PunishedPlayer UUID: " + plexPlayer.getUuid());
if (rollBack)
{
if (plugin.getPrismHook() != null && plugin.getPrismHook().hasPrism())
{
PrismPaperApi prism = plugin.getPrismHook().getPrism();
ActivityQuery query = PaperActivityQuery.builder()
.actionTypeKeys(Arrays.asList("block-place", "block-break", "block-burn", "entity-spawn", "entity-kill", "entity-explode"))
.causePlayerName(plexPlayer.getName())
.before(Instant.now().getEpochSecond())
.after(Instant.now().getEpochSecond() - 86400)
.rollback()
.build();
prism.rollback(sender, query).whenCompleteAsync((result, error) ->
{
if (error != null)
{
send(sender, messageComponent("prismRollbackError", error.getMessage()));
PlexLog.error("Unable to rollback: {0}", error);
return;
}
int count = result.applied();
if (count == 0)
{
send(sender, messageComponent("prismNoResult", count));
PlexLog.debug("No activities are available to rollback");
return;
}
send(sender, messageComponent("prismRollbackMessage", count));
PlexLog.debug("Rolled back {0} activities", count);
});
}
else if (plugin.getCoreProtectHook() != null && plugin.getCoreProtectHook().hasCoreProtect())
{
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask ->
{
plugin.getCoreProtectHook().coreProtectAPI().performRollback(86400, Collections.singletonList(plexPlayer.getName()), null, null, null, null, 0, null);
});
}
}
});
return null;
@@ -11,11 +11,9 @@ import dev.plex.player.PlexPlayer;
import dev.plex.punishment.Punishment;
import dev.plex.punishment.PunishmentType;
import dev.plex.util.BungeeUtil;
import dev.plex.util.PlexLog;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
import java.util.Collections;
import java.util.List;
import net.kyori.adventure.text.Component;
@@ -70,49 +68,16 @@ public class TempbanCMD extends ServerCommand
punishment.setEndDate(TimeUtils.createDate(args[1]));
punishment.setCustomTime(false);
punishment.setActive(true);
if (player != null)
{
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
}
punishment.setIp(target.getIps().getLast());
plugin.getPunishmentManager().punish(target, punishment);
PlexUtils.broadcast(messageComponent("banningPlayer", sender.getName(), target.getName()));
if (player != null)
{
BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService()));
plugin.getApi().scheduler().runEntity(player, () -> BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService())));
}
if (rollBack)
{
/*if (plugin.getPrismHook().hasPrism()) {
PrismParameters parameters = plugin.getPrismHook().prismApi().createParameters();
parameters.addActionType("block-place");
parameters.addActionType("block-break");
parameters.addActionType("block-burn");
parameters.addActionType("entity-spawn");
parameters.addActionType("entity-kill");
parameters.addActionType("entity-explode");
parameters.addPlayerName(plexPlayer.getName());
parameters.setBeforeTime(Instant.now().toEpochMilli());
parameters.setProcessType(PrismProcessType.ROLLBACK);
final Future<Result> result = plugin.getPrismHook().prismApi().performLookup(parameters, sender);
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> {
try
{
final Result done = result.get();
} catch (InterruptedException | ExecutionException e)
{
throw new RuntimeException(e);
}
});
}
else */
if (plugin.getCoreProtectHook() != null && plugin.getCoreProtectHook().hasCoreProtect())
{
PlexLog.debug("Testing coreprotect");
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask ->
{
plugin.getCoreProtectHook().coreProtectAPI().performRollback(86400, Collections.singletonList(target.getName()), null, null, null, null, 0, null);
});
}
plugin.getApi().rollback().rollbackLastDay(sender, target.getName());
}
return null;
}
@@ -0,0 +1,103 @@
package dev.plex.hook;
import dev.plex.Plex;
import dev.plex.api.rollback.RollbackApi;
import dev.plex.util.PlexLog;
import dev.plex.util.PlexUtils;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.prism_mc.prism.api.activities.ActivityQuery;
import org.prism_mc.prism.paper.api.activities.PaperActivityQuery;
public class RollbackManager implements RollbackApi
{
private static final List<String> ROLLBACK_ACTIONS = List.of("block-place", "block-break", "block-burn", "entity-spawn", "entity-kill", "entity-explode");
private final Plex plugin;
public RollbackManager(Plex plugin)
{
this.plugin = plugin;
}
@Override
public boolean isAvailable()
{
return (plugin.getPrismHook() != null && plugin.getPrismHook().hasPrism())
|| (plugin.getCoreProtectHook() != null && plugin.getCoreProtectHook().hasCoreProtect());
}
@Override
public boolean rollback(CommandSender sender, String playerName, int seconds)
{
if (plugin.getPrismHook() != null && plugin.getPrismHook().hasPrism())
{
plugin.getApi().scheduler().runGlobal(() -> rollbackWithPrism(sender, playerName, seconds));
return true;
}
if (plugin.getCoreProtectHook() != null && plugin.getCoreProtectHook().hasCoreProtect())
{
rollbackWithCoreProtect(playerName, seconds);
return true;
}
return false;
}
private void rollbackWithPrism(CommandSender sender, String playerName, int seconds)
{
long now = Instant.now().getEpochSecond();
ActivityQuery query = PaperActivityQuery.builder()
.actionTypeKeys(ROLLBACK_ACTIONS)
.causePlayerName(playerName)
.before(now)
.after(now - seconds)
.rollback()
.build();
plugin.getPrismHook().getPrism().rollback(sender, query).whenComplete((result, error) ->
plugin.getApi().scheduler().runGlobal(() ->
{
if (error != null)
{
sendMessage(sender, PlexUtils.messageComponent("prismRollbackError", error.getMessage()));
PlexLog.error("Unable to rollback: {0}", error);
return;
}
int count = result.applied();
if (count == 0)
{
sendMessage(sender, PlexUtils.messageComponent("prismNoResult", count));
PlexLog.debug("No activities are available to rollback");
return;
}
sendMessage(sender, PlexUtils.messageComponent("prismRollbackMessage", count));
PlexLog.debug("Rolled back {0} activities", count);
}));
}
private void rollbackWithCoreProtect(String playerName, int seconds)
{
plugin.getApi().scheduler().runAsync(() ->
plugin.getCoreProtectHook().coreProtectAPI().performRollback(seconds, Collections.singletonList(playerName), null, null, null, null, 0, null));
}
private void sendMessage(CommandSender sender, Component message)
{
if (sender instanceof Player player)
{
plugin.getApi().scheduler().runEntity(player, () -> sender.sendMessage(message));
return;
}
sender.sendMessage(message);
}
}
@@ -36,12 +36,12 @@ public class ChatListener extends ServerListenerBase
matchResult.group()
))).build();
public static BiConsumer<AsyncChatEvent, PlexPlayer> PRE_RENDERER = ChatListener::defaultChatProcessing;
private final PlexChatRenderer renderer = new PlexChatRenderer();
@EventHandler(priority = EventPriority.LOWEST)
public void onChat(AsyncChatEvent event)
{
PlexPlayer plexPlayer = plugin.getPlayerCache().getPlexPlayerMap().get(event.getPlayer().getUniqueId());
PlexChatRenderer renderer = new PlexChatRenderer();
renderer.format = SafeMiniMessage.mmDeserialize(plugin.config.getString("chat.format"));
if (plexPlayer.isStaffChat())
{
@@ -9,7 +9,6 @@ import dev.plex.util.PlexUtils;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -90,7 +89,7 @@ public class PlayerListener extends ServerListenerBase
PlexPlayer player = plugin.getPlayerService().getPlayer(event.getPlayer().getUniqueId());
if (player.isLockedUp())
{
Bukkit.getGlobalRegionScheduler().runDelayed(plugin, (scheduledTask) -> event.getPlayer().openInventory(event.getInventory()), 1L);
event.getPlayer().getScheduler().runDelayed(plugin, scheduledTask -> event.getPlayer().openInventory(event.getInventory()), null, 1L);
}
}
@@ -5,7 +5,6 @@ import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import dev.plex.Plex;
import dev.plex.player.PlexPlayer;
import dev.plex.storage.StorageExecutor;
import dev.plex.util.PlexLog;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
@@ -25,7 +24,6 @@ import lombok.Data;
import lombok.Getter;
import org.apache.commons.io.FileUtils;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.Nullable;
public class PunishmentManager
@@ -135,7 +133,7 @@ public class PunishmentManager
}
return plugin.getPunishmentRepository().getPunishments(uuid).stream().anyMatch(punishment -> (punishment.getType() == PunishmentType.BAN || punishment.getType() == PunishmentType.TEMPBAN) && punishment.isActive());
}, StorageExecutor.io());
}, plugin.getApi().scheduler().asyncExecutor());
}
public boolean isBanned(UUID uuid)
@@ -194,25 +192,20 @@ public class PunishmentManager
ZonedDateTime now = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
ZonedDateTime then = punishment.getEndDate();
long seconds = ChronoUnit.SECONDS.between(now, then);
new BukkitRunnable()
plugin.getApi().scheduler().runGlobalLater(scheduledTask ->
{
@Override
public void run()
PlexPlayer afterPlayer = plugin.getPlayerService().getPlayer(player.getUuid());
if (!afterPlayer.isFrozen())
{
PlexPlayer afterPlayer = plugin.getPlayerService().getPlayer(player.getUuid());
if (!afterPlayer.isFrozen())
{
this.cancel();
return;
}
afterPlayer.setFrozen(false);
punishment.setActive(false);
plugin.getPunishmentRepository().updatePunishment(punishment.getType(), false, punishment.getPunished());
plugin.getPlayerService().update(afterPlayer);
Bukkit.broadcast(PlexUtils.messageComponent("unfrozePlayer", "Plex", Bukkit.getOfflinePlayer(afterPlayer.getUuid()).getName()));
return;
}
}.runTaskLater(plugin, 20 * seconds);
afterPlayer.setFrozen(false);
punishment.setActive(false);
plugin.getPunishmentRepository().updatePunishment(punishment.getType(), false, punishment.getPunished());
plugin.getPlayerService().update(afterPlayer);
Bukkit.broadcast(PlexUtils.messageComponent("unfrozePlayer", "Plex", Bukkit.getOfflinePlayer(afterPlayer.getUuid()).getName()));
}, Math.max(1L, 20L * seconds));
}
else if (punishment.getType() == PunishmentType.MUTE)
{
@@ -220,24 +213,19 @@ public class PunishmentManager
ZonedDateTime now = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
ZonedDateTime then = punishment.getEndDate();
long seconds = ChronoUnit.SECONDS.between(now, then);
new BukkitRunnable()
plugin.getApi().scheduler().runGlobalLater(scheduledTask ->
{
@Override
public void run()
PlexPlayer afterPlayer = plugin.getPlayerService().getPlayer(player.getUuid());
if (!afterPlayer.isMuted())
{
PlexPlayer afterPlayer = plugin.getPlayerService().getPlayer(player.getUuid());
if (!afterPlayer.isMuted())
{
this.cancel();
return;
}
afterPlayer.setMuted(false);
punishment.setActive(false);
plugin.getPunishmentRepository().updatePunishment(punishment.getType(), false, punishment.getPunished());
Bukkit.broadcast(PlexUtils.messageComponent("unmutedPlayer", "Plex", Bukkit.getOfflinePlayer(afterPlayer.getUuid()).getName()));
return;
}
}.runTaskLater(plugin, 20 * seconds);
afterPlayer.setMuted(false);
punishment.setActive(false);
plugin.getPunishmentRepository().updatePunishment(punishment.getType(), false, punishment.getPunished());
Bukkit.broadcast(PlexUtils.messageComponent("unmutedPlayer", "Plex", Bukkit.getOfflinePlayer(afterPlayer.getUuid()).getName()));
}, Math.max(1L, 20L * seconds));
}
}
@@ -7,8 +7,11 @@ import dev.plex.services.impl.BanService;
import dev.plex.services.impl.GameRuleService;
import dev.plex.services.impl.TimingService;
import dev.plex.services.impl.UpdateCheckerService;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.bukkit.Bukkit;
@@ -17,6 +20,7 @@ public class ServiceManager
{
private final Plex plugin;
private final List<AbstractService> services = Lists.newArrayList();
private final Map<AbstractService, ScheduledTask> scheduledTasks = new HashMap<>();
public ServiceManager(Plex plugin)
{
@@ -45,25 +49,36 @@ public class ServiceManager
public void startService(AbstractService service)
{
ScheduledTask existingTask = scheduledTasks.remove(service);
if (existingTask != null)
{
existingTask.cancel();
}
ScheduledTask task = null;
if (!service.isRepeating())
{
int time = service.repeatInSeconds();
if (time == 0)
{
Bukkit.getGlobalRegionScheduler().run(plugin, service::run);
task = Bukkit.getGlobalRegionScheduler().run(plugin, service::run);
}
else
{
Bukkit.getAsyncScheduler().runDelayed(plugin, service::run, time, TimeUnit.SECONDS);
task = Bukkit.getAsyncScheduler().runDelayed(plugin, service::run, time, TimeUnit.SECONDS);
}
}
else if (service.isRepeating() && service.isAsynchronous())
{
Bukkit.getAsyncScheduler().runAtFixedRate(plugin, service::run, 1, service.repeatInSeconds(), TimeUnit.SECONDS);
task = Bukkit.getAsyncScheduler().runAtFixedRate(plugin, service::run, 1, service.repeatInSeconds(), TimeUnit.SECONDS);
}
else if (service.isRepeating() && !service.isAsynchronous())
{
Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, service::run, 1, 20L * service.repeatInSeconds());
task = Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, service::run, 1, 20L * service.repeatInSeconds());
}
if (task != null)
{
scheduledTasks.put(service, task);
}
if (!services.contains(service))
{
@@ -74,8 +89,11 @@ public class ServiceManager
public void endService(AbstractService service, boolean remove)
{
Bukkit.getGlobalRegionScheduler().cancelTasks(plugin);
Bukkit.getAsyncScheduler().cancelTasks(plugin);
ScheduledTask task = scheduledTasks.remove(service);
if (task != null)
{
task.cancel();
}
service.onEnd();
if (remove)
{
@@ -30,33 +30,16 @@ public class AutoWipeService extends AbstractService
{
if (entities.stream().anyMatch(entityName -> entityName.equalsIgnoreCase(entity.getType().name())))
{
Bukkit.getRegionScheduler().run(plugin, entity.getLocation(), this::entityRun);
entity.getScheduler().run(plugin, scheduledTask -> entity.remove(), null);
}
}
}
}
}
private void entityRun(ScheduledTask task)
{
List<String> entities = plugin.config.getStringList("autowipe.entities");
for (World world : Bukkit.getWorlds())
{
for (Entity entity : world.getEntities())
{
if (entities.stream().anyMatch(entityName -> entityName.equalsIgnoreCase(entity.getType().name())))
{
entity.remove();
task.cancel();
}
}
}
}
@Override
public int repeatInSeconds()
{
return Math.max(1, plugin.config.getInt("autowipe.interval"));
}
}
}
@@ -55,7 +55,7 @@ public class RedisConnection
public void runAsync(Consumer<Jedis> jedisConsumer)
{
StorageExecutor.io().execute(() -> execute(jedisConsumer));
plugin.getApi().scheduler().runAsync(() -> execute(jedisConsumer));
}
public final boolean isEnabled()
@@ -1,36 +0,0 @@
package dev.plex.storage;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public final class StorageExecutor
{
private static final AtomicInteger THREAD_ID = new AtomicInteger();
private static final ExecutorService IO = Executors.newFixedThreadPool(Math.max(2, Runtime.getRuntime().availableProcessors() / 2), new ThreadFactory()
{
@Override
public Thread newThread(Runnable runnable)
{
Thread thread = new Thread(runnable, "Plex Storage IO-" + THREAD_ID.incrementAndGet());
thread.setDaemon(true);
return thread;
}
});
private StorageExecutor()
{
}
public static Executor io()
{
return IO;
}
public static void shutdown()
{
IO.shutdownNow();
}
}
@@ -6,7 +6,6 @@ import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.stmt.DeleteBuilder;
import dev.plex.punishment.extra.Note;
import dev.plex.storage.StorageExecutor;
import dev.plex.storage.database.entity.NoteEntity;
import dev.plex.storage.repository.NoteRepository;
import dev.plex.util.TimeUtils;
@@ -19,16 +18,19 @@ import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class SQLNotes implements NoteRepository
{
private final Dao<NoteEntity, Long> notes;
private final Executor executor;
public SQLNotes(ConnectionSource connectionSource)
public SQLNotes(ConnectionSource connectionSource, Executor executor)
{
try
{
this.notes = DaoManager.createDao(connectionSource, NoteEntity.class);
this.executor = executor;
}
catch (SQLException e)
{
@@ -52,7 +54,7 @@ public class SQLNotes implements NoteRepository
e.printStackTrace();
return Lists.newArrayList();
}
}, StorageExecutor.io());
}, executor);
}
public CompletableFuture<Void> deleteNote(int id, UUID uuid)
@@ -69,7 +71,7 @@ public class SQLNotes implements NoteRepository
{
e.printStackTrace();
}
}, StorageExecutor.io());
}, executor);
}
public CompletableFuture<Void> addNote(Note note)
@@ -91,7 +93,7 @@ public class SQLNotes implements NoteRepository
{
e.printStackTrace();
}
}, StorageExecutor.io());
}, executor);
}
private Note toNote(NoteEntity entity)
@@ -7,7 +7,6 @@ import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.stmt.UpdateBuilder;
import dev.plex.punishment.Punishment;
import dev.plex.punishment.PunishmentType;
import dev.plex.storage.StorageExecutor;
import dev.plex.storage.database.entity.PunishmentEntity;
import dev.plex.storage.repository.PunishmentRepository;
import dev.plex.util.PlexLog;
@@ -20,16 +19,19 @@ import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class SQLPunishment implements PunishmentRepository
{
private final Dao<PunishmentEntity, Long> punishments;
private final Executor executor;
public SQLPunishment(ConnectionSource connectionSource)
public SQLPunishment(ConnectionSource connectionSource, Executor executor)
{
try
{
this.punishments = DaoManager.createDao(connectionSource, PunishmentEntity.class);
this.executor = executor;
}
catch (SQLException e)
{
@@ -50,7 +52,7 @@ public class SQLPunishment implements PunishmentRepository
e.printStackTrace();
return Lists.newArrayList();
}
}, StorageExecutor.io());
}, executor);
}
public List<Punishment> getPunishments(UUID uuid)
@@ -92,7 +94,7 @@ public class SQLPunishment implements PunishmentRepository
{
e.printStackTrace();
}
}, StorageExecutor.io());
}, executor);
}
public void syncRemoveBan(UUID uuid)
@@ -103,12 +105,12 @@ public class SQLPunishment implements PunishmentRepository
public CompletableFuture<Void> updatePunishment(PunishmentType type, boolean active, UUID punished)
{
return CompletableFuture.runAsync(() -> setActive(punished, type, active), StorageExecutor.io());
return CompletableFuture.runAsync(() -> setActive(punished, type, active), executor);
}
public CompletableFuture<Void> removeBan(UUID uuid)
{
return CompletableFuture.runAsync(() -> syncRemoveBan(uuid), StorageExecutor.io());
return CompletableFuture.runAsync(() -> syncRemoveBan(uuid), executor);
}
private void setActive(UUID punished, PunishmentType type, boolean active)
@@ -12,7 +12,6 @@ import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import lombok.NonNull;
@@ -21,6 +20,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.commons.io.FileUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.json.JSONException;
public class UpdateChecker
@@ -121,7 +121,7 @@ public class UpdateChecker
{
if (verbosity == 2)
{
sender.sendMessage(Component.text("There was an error checking for updates.").color(NamedTextColor.RED));
sendMessage(sender, Component.text("There was an error checking for updates.").color(NamedTextColor.RED));
}
return false;
}
@@ -129,7 +129,7 @@ public class UpdateChecker
{
if (verbosity == 2)
{
sender.sendMessage(Component.text("Plex is up to date!").color(NamedTextColor.GREEN));
sendMessage(sender, Component.text("Plex is up to date!").color(NamedTextColor.GREEN));
}
return false;
}
@@ -137,7 +137,7 @@ public class UpdateChecker
{
if (verbosity == 2)
{
sender.sendMessage(Component.text("Unknown version, unable to check for updates.").color(NamedTextColor.RED));
sendMessage(sender, Component.text("Unknown version, unable to check for updates.").color(NamedTextColor.RED));
}
return false;
}
@@ -145,9 +145,9 @@ public class UpdateChecker
{
if (verbosity >= 1)
{
sender.sendMessage(Component.text("Plex is not up to date!", NamedTextColor.RED));
sender.sendMessage(Component.text("Download a new version at: " + DOWNLOAD_PAGE + "Plex").color(NamedTextColor.RED));
sender.sendMessage(Component.text("Or run: /plex update").color(NamedTextColor.RED));
sendMessage(sender, Component.text("Plex is not up to date!", NamedTextColor.RED));
sendMessage(sender, Component.text("Download a new version at: " + DOWNLOAD_PAGE + "Plex").color(NamedTextColor.RED));
sendMessage(sender, Component.text("Or run: /plex update").color(NamedTextColor.RED));
}
return true;
}
@@ -174,7 +174,7 @@ public class UpdateChecker
JsonObject object = new Gson().fromJson(reader, JsonObject.class);
JsonObject artifact = object.getAsJsonArray("artifacts").asList().getFirst().getAsJsonObject();
String jarFile = artifact.get("fileName").getAsString();
sender.sendMessage(PlexUtils.mmDeserialize("<green>Downloading latest JAR file: " + jarFile));
sendMessage(sender, PlexUtils.mmDeserialize("<green>Downloading latest JAR file: " + jarFile));
File copyTo;
if (!module)
{
@@ -184,7 +184,7 @@ public class UpdateChecker
{
copyTo = new File(plugin.getModulesFolder().getPath(), jarFile);
}
CompletableFuture.runAsync(() ->
plugin.getApi().scheduler().runAsync(() ->
{
try
{
@@ -192,7 +192,7 @@ public class UpdateChecker
URI.create(url + "/lastSuccessfulBuild/artifact/build/libs/" + jarFile).toURL(),
copyTo
);
sender.sendMessage(PlexUtils.mmDeserialize("<green>New JAR file downloaded successfully."));
sendMessage(sender, PlexUtils.mmDeserialize("<green>New JAR file downloaded successfully."));
}
catch (IOException e)
{
@@ -207,11 +207,11 @@ public class UpdateChecker
}
else if (statusCode == HttpURLConnection.HTTP_NOT_FOUND)
{
sender.sendMessage(PlexUtils.mmDeserialize("<red>Could not update " + name + " as it can't be found on Jenkins."));
sendMessage(sender, PlexUtils.mmDeserialize("<red>Could not update " + name + " as it can't be found on Jenkins."));
}
else
{
sender.sendMessage(PlexUtils.mmDeserialize("<red>Something went wrong while trying to update " + name + ". Please check the log for more information."));
sendMessage(sender, PlexUtils.mmDeserialize("<red>Something went wrong while trying to update " + name + ". Please check the log for more information."));
PlexLog.error("Unable to update module {0} due to unexpected status code returned from Jenkins - Status Code: {1}", name, statusCode);
}
}
@@ -221,9 +221,19 @@ public class UpdateChecker
}
catch (JSONException e)
{
sender.sendMessage(PlexUtils.mmDeserialize("<red>Something went wrong while trying to gather information from Jenkins for " + name + ". Please check the log for more information"));
sendMessage(sender, PlexUtils.mmDeserialize("<red>Something went wrong while trying to gather information from Jenkins for " + name + ". Please check the log for more information"));
PlexLog.error("Unable to parse JSON information received from Jenkins - see below for more information...");
e.printStackTrace();
}
}
private void sendMessage(CommandSender sender, Component message)
{
if (sender instanceof Player player)
{
plugin.getApi().scheduler().runEntity(player, () -> sender.sendMessage(message));
return;
}
plugin.getApi().scheduler().runGlobal(() -> sender.sendMessage(message));
}
}