Compare commits

..

2 Commits

14 changed files with 171 additions and 430 deletions

View File

@ -47,8 +47,4 @@ dependencies {
compileOnly("org.projectlombok:lombok:1.18.38")
annotationProcessor("org.projectlombok:lombok:1.18.38")
implementation(platform("com.intellectualsites.bom:bom-newest:1.56"))
compileOnly("com.intellectualsites.plotsquared:plotsquared-core")
compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit") { isTransitive = false }
}

View File

@ -14,9 +14,8 @@ dependencyResolutionManagement {
password = nexusPass
}
}
maven("https://repo.destro.xyz/snapshots")
maven("https://jitpack.io")
// PlotSquared
maven("https://repo.papermc.io/repository/maven-public/")
}
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
}

View File

@ -1,43 +1,36 @@
package com.alttd.playerutils;
import com.alttd.playerutils.commands.PlayerUtilsCommand;
import com.alttd.playerutils.commands.playerutils_subcommands.AprilFools;
import com.alttd.playerutils.commands.playerutils_subcommands.GhastSpeed;
import com.alttd.playerutils.commands.playerutils_subcommands.RandomPlot;
import com.alttd.playerutils.commands.playerutils_subcommands.RotateBlock;
import com.alttd.playerutils.config.Config;
import com.alttd.playerutils.config.KeyStorage;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.event_listeners.*;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.concurrent.TimeUnit;
@Slf4j
import com.alttd.playerutils.util.AprilFoolsPrank;
public final class PlayerUtils extends JavaPlugin {
private PlayerUtilsCommand playerUtilsCommand;
private AprilFoolsPrank aprilFoolsPrank;
@Override
public void onEnable() {
reloadConfigs();
// initialize prank utility
aprilFoolsPrank = new AprilFoolsPrank(this);
registerCommands();
registerRandomPlot();
registerEvents();
reloadConfigs();
registerSchedulers();
}
private void registerRandomPlot() {
if (!getServer().getPluginManager().isPluginEnabled("PlotSquared")) {
log.warn("PlotSquared not found — random plot command will not be registered.");
return;
}
playerUtilsCommand.addSubCommand(new RandomPlot(this));
log.info("PlotSquared found - registered random plot command.");
}
@Override
public void onDisable() {
KeyStorage.STORAGE.save();
@ -45,6 +38,8 @@ public final class PlayerUtils extends JavaPlugin {
private void registerCommands() {
playerUtilsCommand = new PlayerUtilsCommand(this);
// add april fools test command
playerUtilsCommand.addSubCommand(new AprilFools(aprilFoolsPrank));
}
private void registerEvents() {
@ -57,7 +52,6 @@ public final class PlayerUtils extends JavaPlugin {
pluginManager.registerEvents(new PlayerJoin(this), this);
pluginManager.registerEvents(new BookWriteEvent(), this);
pluginManager.registerEvents(new BookByteLimitListener(), this);
pluginManager.registerEvents(new VillagerWorkstationEvent(this), this);
RotateBlockEvent rotateBlockEvent = new RotateBlockEvent();
pluginManager.registerEvents(rotateBlockEvent, this);
@ -75,7 +69,11 @@ public final class PlayerUtils extends JavaPlugin {
}
private void registerSchedulers() {
// periodic key storage save (async)
Bukkit.getScheduler().runTaskTimerAsynchronously(this, KeyStorage.STORAGE::save,
TimeUnit.MINUTES.toSeconds(5) * 20, TimeUnit.MINUTES.toSeconds(5) * 20);
// April 1st prank scheduler
aprilFoolsPrank.schedule();
}
}

View File

@ -0,0 +1,48 @@
package com.alttd.playerutils.commands.playerutils_subcommands;
import com.alttd.playerutils.commands.SubCommand;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.util.AprilFoolsPrank;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
public class AprilFools extends SubCommand {
private final AprilFoolsPrank prank;
public AprilFools(AprilFoolsPrank prank) {
this.prank = prank;
}
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
if (!(commandSender instanceof Player player)) {
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
return true;
}
boolean ok = prank.playSoundAroundPlayer(player);
if (ok) {
commandSender.sendRichMessage("<green>April Fools test triggered. Listen closely...");
} else {
commandSender.sendRichMessage("<red>Failed to trigger. You must be in the overworld named 'world'.");
}
return true;
}
@Override
public String getName() {
return "aprilfools";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return List.of();
}
@Override
public String getHelpMessage() {
return "<gray>/playerutils aprilfools</gray> <dark_gray>-</dark_gray> <gray>Play a fake explosion near you (testing).";
}
}

View File

@ -42,7 +42,7 @@ import java.util.stream.Collectors;
.forEach(team -> team.removePlayer(player));
if (args[1].equalsIgnoreCase("off")) {
turnOffGlow(commandSender, player, otherPlayer, board);
turnOffGlow(commandSender, player, otherPlayer);
return true;
}
@ -88,15 +88,10 @@ import java.util.stream.Collectors;
}
}
private void turnOffGlow(CommandSender commandSender, Player player, boolean otherPlayer, Scoreboard board) {
private void turnOffGlow(CommandSender commandSender, Player player, boolean otherPlayer) {
player.sendRichMessage(Messages.GLOW.GLOW_OFF);
player.setGlowing(false);
board.getTeams().stream()
.filter(team -> team.getName().startsWith("Glow-"))
.filter(team -> team.hasPlayer(player))
.forEach(team -> team.removePlayer(player));
if (otherPlayer) {
commandSender.sendRichMessage(Messages.GLOW.GLOW_OFF_FOR_PLAYER, Placeholder.component("player", player.name()));
}

View File

@ -1,181 +0,0 @@
package com.alttd.playerutils.commands.playerutils_subcommands;
import com.alttd.playerutils.PlayerUtils;
import com.alttd.playerutils.commands.SubCommand;
import com.alttd.playerutils.config.Config;
import com.alttd.playerutils.config.Messages;
import com.plotsquared.bukkit.player.BukkitPlayer;
import com.plotsquared.bukkit.util.BukkitUtil;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.events.TeleportCause;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.PlotArea;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.title.Title;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
@Slf4j
@RequiredArgsConstructor
public class RandomPlot extends SubCommand {
private static final String PERMISSION = "playerutils.randomplot";
private final PlayerUtils plugin;
private final MiniMessage miniMessage = MiniMessage.miniMessage();
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
if (!(commandSender instanceof Player player)) {
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
return true;
}
String worldName = player.getWorld().getName();
if (!Config.RANDOM_PLOT.ALLOWED_WORLDS.contains(worldName)) {
player.sendRichMessage(Messages.RANDOM_PLOT.WORLD_NOT_ALLOWED);
return true;
}
if (!player.hasPermission(PERMISSION)) {
player.sendRichMessage(Messages.GENERIC.NO_PERMISSION, Placeholder.parsed("permission", PERMISSION));
return true;
}
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
player.sendRichMessage(Messages.RANDOM_PLOT.PLOT_SQUARED_NOT_ENABLED);
return true;
}
List<Plot> plots = collectPlots(worldName);
if (plots.isEmpty()) {
player.sendRichMessage(Messages.RANDOM_PLOT.NO_PLOTS_FOUND);
return true;
}
Plot target = plots.get(ThreadLocalRandom.current().nextInt(plots.size()));
player.sendRichMessage(Messages.RANDOM_PLOT.TELEPORT_START);
startCountdown(player, target);
return true;
}
// PlotSquared helpers
private List<Plot> collectPlots(String worldName) {
try {
return PlotSquared.get().getPlotAreaManager().getPlotAreasSet(worldName)
.stream()
.map(PlotArea::getPlots)
.flatMap(Collection::stream)
.toList();
} catch (Exception e) {
log.error("Failed to retrieve plots from PlotSquared for world '{}'", worldName, e);
}
return List.of();
}
private void teleportToPlot(Player player, Plot plot) {
try {
BukkitPlayer bPlayer = BukkitUtil.adapt(player);
plot.teleportPlayer(bPlayer, TeleportCause.PLUGIN, success -> {
if (Boolean.TRUE.equals(success)) {
player.sendRichMessage(Messages.RANDOM_PLOT.TELEPORT_SUCCESS,
Placeholder.parsed("plot_id", plot.getId().toSeparatedString(";")),
Placeholder.parsed("plot_owner", getOwner(plot)));
}
});
} catch (Exception e) {
log.error("Failed to teleport {} to plot {}", player.getName(), plot.getId(), e);
}
}
private String getOwner(Plot plot) {
String ownerName;
UUID owner = plot.getOwner();
if (owner == null) {
ownerName = "Unknown";
} else {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(owner);
ownerName = offlinePlayer.getName();
if (ownerName == null) {
ownerName = "Unknown";
}
}
return ownerName;
}
// Countdown
private void startCountdown(Player player, Plot plot) {
final Location origin = player.getLocation().clone();
final int[] secondsLeft = {Config.RANDOM_PLOT.COUNTDOWN_SECONDS};
new BukkitRunnable() {
@Override
public void run() {
if (!player.isOnline()) {
cancel();
return;
}
// Movement check cancel if player moved more than 0.5 blocks on any axis
Location current = player.getLocation();
if (Math.abs(current.getX() - origin.getX()) > 0.5
|| Math.abs(current.getY() - origin.getY()) > 0.5
|| Math.abs(current.getZ() - origin.getZ()) > 0.5) {
cancel();
player.sendRichMessage(Messages.RANDOM_PLOT.TELEPORT_CANCELLED);
return;
}
if (secondsLeft[0] <= 0) {
cancel();
teleportToPlot(player, plot);
return;
}
// Show countdown title for current second
String rawTitle = Config.RANDOM_PLOT.COUNTDOWN_TITLES
.getOrDefault(secondsLeft[0], String.valueOf(secondsLeft[0]));
Component title = miniMessage.deserialize(rawTitle);
Component subtitle = miniMessage.deserialize(Config.RANDOM_PLOT.COUNTDOWN_SUBTITLE);
player.showTitle(Title.title(
title,
subtitle,
Title.Times.times(Duration.ZERO, Duration.ofMillis(1200), Duration.ofMillis(200))
));
secondsLeft[0]--;
}
}.runTaskTimer(plugin, 0L, 20L);
}
@Override
public String getName() {
return "randomplot";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.RANDOM_PLOT;
}
}

View File

@ -5,20 +5,18 @@ import org.bukkit.configuration.ConfigurationSection;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
@Slf4j
public class Config extends AbstractConfig {
@Slf4j public class Config extends AbstractConfig{
static Config config;
Config() {
super(
new File(File.separator
+ "mnt" + File.separator
+ "configs" + File.separator
+ "PlayerUtils"),
+ "mnt" + File.separator
+ "configs" + File.separator
+ "PlayerUtils"),
"config.yml");
}
@ -94,61 +92,4 @@ public class Config extends AbstractConfig {
WAYPOINT_TRANSMIT_RANGE = config.getDouble(prefix, "waypoint_transmit_range", WAYPOINT_TRANSMIT_RANGE);
}
}
public static class VILLAGER_WORKSTATION {
private static final String prefix = "villager-workstation.";
public static int RANGE = 8;
public static int CHECK_INTERVAL_TICKS = 5;
public static int PARTICLE_RING_COUNT = 8;
@SuppressWarnings("unused")
private static void load() {
RANGE = config.getInt(prefix, "range", RANGE);
CHECK_INTERVAL_TICKS = config.getInt(prefix, "check-interval-ticks", CHECK_INTERVAL_TICKS);
PARTICLE_RING_COUNT = config.getInt(prefix, "particle-ring-count", PARTICLE_RING_COUNT);
}
}
public static class RANDOM_PLOT {
private static final String prefix = "random-plot.";
// Command
public static List<String> ALLOWED_WORLDS = List.of("plotworld");
// Permissions
public static int COUNTDOWN_SECONDS = 3;
public static HashMap<Integer, String> COUNTDOWN_TITLES = new HashMap<>();
public static String COUNTDOWN_SUBTITLE = "<dark_grey>Preparing teleportation</dark_grey>";
@SuppressWarnings("unused")
private static void load() {
ALLOWED_WORLDS = config.getStringList(prefix, "allowed-worlds", ALLOWED_WORLDS);
COUNTDOWN_SECONDS = config.getInt(prefix + "countdown.", "seconds", COUNTDOWN_SECONDS);
COUNTDOWN_SUBTITLE = config.getString(prefix + "countdown.", "subtitle", COUNTDOWN_SUBTITLE);
// Countdown titles integer keys map to display strings
COUNTDOWN_TITLES.clear();
ConfigurationSection titlesSection =
config.getConfigurationSection("random-plot.countdown.titles");
if (titlesSection != null) {
for (String key : titlesSection.getKeys(false)) {
try {
COUNTDOWN_TITLES.put(Integer.parseInt(key),
titlesSection.getString(key, key));
} catch (NumberFormatException ignored) {
log.warn("Invalid countdown title key: {}", key);
}
}
} else {
config.yaml.addDefault("random-plot.countdown.titles.3", "<color:#08FBFF>Go!");
config.yaml.addDefault("random-plot.countdown.titles.2", "<color:#08FBFF>2");
config.yaml.addDefault("random-plot.countdown.titles.1", "<color:#08FBFF>1");
COUNTDOWN_TITLES.put(3, "<color:#08FBFF>2");
COUNTDOWN_TITLES.put(2, "<color:#08FBFF>1");
COUNTDOWN_TITLES.put(1, "<color:#08FBFF>Go!");
}
}
}
}

View File

@ -1,5 +1,7 @@
package com.alttd.playerutils.config;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.List;
@ -33,7 +35,6 @@ public class Messages extends AbstractConfig {
public static String KEY = "<green>Receive a key that you are owed: <gold>/pu key</gold></green>";
public static String GHAST_SPEED = "<green>Set the speed of a ghast: <gold>/pu ghastspeed <speed></gold></green>";
public static String RECOUNT_ARMOR_STANDS = "<green>Recount armor stands in current chunk: <gold>/pu recount</gold></green>";
public static String RANDOM_PLOT = "<green>Get a random plot: <gold>/pu randomplot</gold></green>";
@SuppressWarnings("unused")
private static void load() {
@ -196,39 +197,4 @@ public class Messages extends AbstractConfig {
SUCCESS = config.getString(prefix, "success", SUCCESS);
}
}
public static class VILLAGER_WORKSTATION {
private static final String prefix = "villager-workstation.";
public static String WORKSTATION = "<gold>Villager (<profession>): <yellow><workstation></yellow></gold>";
public static String NO_WORKSTATION = "<gold>Villager (<profession>): <gray>No workstation</gray></gold>";
@SuppressWarnings("unused")
private static void load() {
WORKSTATION = config.getString(prefix, "workstation", WORKSTATION);
NO_WORKSTATION = config.getString(prefix, "no-workstation", NO_WORKSTATION);
}
}
public static class RANDOM_PLOT {
private static final String prefix = "random-plot.";
public static String WORLD_NOT_ALLOWED = "<red>You must be in an allowed world to use this command.</red>";
public static String PLOT_SQUARED_NOT_ENABLED = "<red>PlotSquared is not available on this server.</red>";
public static String TELEPORT_START = "<green>Starting teleport countdown...</green>";
public static String TELEPORT_SUCCESS = "<gold>You have arrived at <plot_id> by <plot_owner> random plot!</gold>";
public static String TELEPORT_CANCELLED = "<red>Teleport cancelled! You moved.</red>";
public static String NO_PLOTS_FOUND = "<red>No plots found in this world.</red>";
@SuppressWarnings("unused")
private static void load() {
WORLD_NOT_ALLOWED = config.getString(prefix, "world-not-allowed", WORLD_NOT_ALLOWED);
PLOT_SQUARED_NOT_ENABLED = config.getString(prefix, "plotsquared-not-enabled", PLOT_SQUARED_NOT_ENABLED);
TELEPORT_START = config.getString(prefix, "teleport-start", TELEPORT_START);
TELEPORT_SUCCESS = config.getString(prefix, "teleport-success", TELEPORT_SUCCESS);
TELEPORT_CANCELLED = config.getString(prefix, "teleport-cancelled", TELEPORT_CANCELLED);
NO_PLOTS_FOUND = config.getString(prefix, "no-plots-found", NO_PLOTS_FOUND);
}
}
}

View File

@ -23,7 +23,7 @@ public class BookByteLimitListener implements Listener {
private boolean isOversizedBook(ItemStack stack) {
boolean isOversizedBook = BookByteUtils.shouldCountForBookByteLimit(stack)
&& BookByteUtils.computeBytes(stack) > BookByteUtils.getMAX_BOOK_BYTES();
&& BookByteUtils.computeBytes(stack) > BookByteUtils.MAX_BOOK_BYTES;
if (isOversizedBook) {
log.warn("Player tried to drop an oversized book");
Component message = MiniMessage.miniMessage().deserialize(
@ -35,7 +35,7 @@ public class BookByteLimitListener implements Listener {
private boolean isOversizedBook(ItemStack stack, HumanEntity humanEntity) {
boolean isOversizedBook = BookByteUtils.shouldCountForBookByteLimit(stack)
&& BookByteUtils.computeBytes(stack) > BookByteUtils.getMaxBookBytes(humanEntity);
&& BookByteUtils.computeBytes(stack) > BookByteUtils.MAX_BOOK_BYTES;
if (isOversizedBook) {
log.warn("{} [{}] tried to drop an oversized book", humanEntity.getName(), humanEntity.getUniqueId());
Component message = MiniMessage.miniMessage().deserialize(
@ -96,9 +96,7 @@ public class BookByteLimitListener implements Listener {
@EventHandler
public void onInventoryMoveItem(InventoryMoveItemEvent event) {
if (event.getSource().getHolder() instanceof HumanEntity humanEntity && isOversizedBook(event.getItem(), humanEntity)) {
event.setCancelled(true);
} else if (isOversizedBook(event.getItem())) {
if (isOversizedBook(event.getItem())) {
event.setCancelled(true);
}
}

View File

@ -23,7 +23,7 @@ public class BookWriteEvent implements Listener {
int totalBytes = BookByteUtils.computeBytes(meta);
if (totalBytes > BookByteUtils.getMaxBookBytes(player)) {
if (totalBytes > BookByteUtils.MAX_BOOK_BYTES) {
log.warn("Player {} [{}] tried to write a book with {} bytes",
player.getName(), player.getUniqueId(), totalBytes);
event.setCancelled(true);
@ -31,7 +31,7 @@ public class BookWriteEvent implements Listener {
"<red>Player <player> tried to write a book with <bytes> bytes</red>",
Placeholder.unparsed("player", player.getName()), Placeholder.parsed("bytes", String.valueOf(totalBytes)));
Bukkit.broadcast(message, "staffutils.patrol");
} else if (totalBytes > BookByteUtils.getBigBookBytes(player)) {
} else if (totalBytes > BookByteUtils.BIG_BOOK_BYTES) {
log.warn("Player {} [{}] wrote a large book with {} bytes",
player.getName(), player.getUniqueId(), totalBytes);
Component message = MiniMessage.miniMessage().deserialize(

View File

@ -1,84 +0,0 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.config.Config;
import com.alttd.playerutils.config.Messages;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
@Slf4j
public class VillagerWorkstationEvent implements Listener {
public VillagerWorkstationEvent(JavaPlugin plugin) {
Bukkit.getScheduler().runTaskTimer(plugin, this::checkPlayersLookingAtVillagers,
0L, Config.VILLAGER_WORKSTATION.CHECK_INTERVAL_TICKS);
}
private void checkPlayersLookingAtVillagers() {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.getInventory().getItemInMainHand().getType().equals(Material.AIR)) {
continue;
}
Entity target = player.getTargetEntity(Config.VILLAGER_WORKSTATION.RANGE);
if (!(target instanceof Villager villager)) {
continue;
}
showWorkstation(player, villager);
}
}
private void showWorkstation(Player player, Villager villager) {
String professionName = formatName(villager.getProfession().key().asMinimalString());
Location jobSite = villager.getMemory(MemoryKey.JOB_SITE);
if (jobSite == null) {
player.sendActionBar(MiniMessage.miniMessage().deserialize(
Messages.VILLAGER_WORKSTATION.NO_WORKSTATION
.replace("<profession>", professionName)));
return;
}
// Read the actual block type at the linked location so it reflects reality
String blockName = formatName(jobSite.getBlock().getType().name());
player.sendActionBar(MiniMessage.miniMessage().deserialize(
Messages.VILLAGER_WORKSTATION.WORKSTATION
.replace("<profession>", professionName)
.replace("<workstation>", blockName)));
spawnWorkstationParticles(player, jobSite.getBlock().getLocation());
}
private void spawnWorkstationParticles(Player player, Location blockLoc) {
World world = blockLoc.getWorld();
if (world == null) {
return;
}
double cx = blockLoc.getX() + 0.5;
double cy = blockLoc.getY() + 1.1;
double cz = blockLoc.getZ() + 0.5;
int count = Config.VILLAGER_WORKSTATION.PARTICLE_RING_COUNT;
for (int i = 0; i < count; i++) {
double angle = (2 * Math.PI * i) / count;
double x = cx + 0.5 * Math.cos(angle);
double z = cz + 0.5 * Math.sin(angle);
player.spawnParticle(Particle.HAPPY_VILLAGER, x, cy, z, 1, 0, 0, 0, 0);
}
}
private static String formatName(String enumName) {
String lower = enumName.replace("_", " ").toLowerCase();
return Character.toUpperCase(lower.charAt(0)) + lower.substring(1);
}
}

View File

@ -0,0 +1,94 @@
package com.alttd.playerutils.util;
import com.alttd.playerutils.PlayerUtils;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* Encapsulates the April Fools' prank logic. - schedule(): registers the timed prank that only runs on April 1st and
* only during overworld night. - playExplosionAround(Player): immediately plays the explosion sound around the target
* for testing (no date/time checks), but still requires the target to be in the overworld named "world" to match
* intended environment.
*/
@Slf4j
public class AprilFoolsPrank {
private final PlayerUtils plugin;
public AprilFoolsPrank(PlayerUtils plugin) {
this.plugin = plugin;
}
/**
* Register the timed prank task. Safe to call on the main thread during plugin enable.
*/
public void schedule() {
// April 1st prank: during overworld night, every 2 minutes pick one player and play an explosion somewhere in a 30-block radius
Bukkit.getScheduler().runTaskTimer(plugin, () -> {
LocalDate now = LocalDate.now(ZoneId.systemDefault());
if (now.getMonth() != Month.APRIL || now.getDayOfMonth() != 1) {
return; // only active on April 1st
}
World world = Bukkit.getWorld("world");
if (world == null) {
return; // overworld not present
}
List<Player> players = world.getPlayers();
if (players.isEmpty()) {
return;
}
Player target = players.get(ThreadLocalRandom.current().nextInt(players.size()));
playOnce(world, target);
}, 20L, 20L * 60L * 2L); // start after 1s, repeat every 2 minutes
}
/**
* Trigger the prank once around the given player for testing. Returns true if executed. This method ignores the
* date and time checks so it can be tested easily, but it still requires the player to be in the overworld named
* "world".
*/
public boolean playSoundAroundPlayer(Player target) {
if (target == null) {
return false;
}
World world = target.getWorld();
if (!"world".equalsIgnoreCase(world.getName())) {
return false; // only intended for overworld
}
playOnce(world, target);
return true;
}
public List<Sound> getSounds() {
return List.of(Sound.ENTITY_GENERIC_EXPLODE, Sound.BLOCK_BELL_USE, Sound.ENTITY_SPIDER_AMBIENT,
Sound.ENTITY_TNT_PRIMED, Sound.ENTITY_CAT_BEG_FOR_FOOD, Sound.ENTITY_SHULKER_AMBIENT,
Sound.BLOCK_WOODEN_DOOR_CLOSE);
}
private void playOnce(World world, Player target) {
Location base = target.getLocation();
double radius = 25.0;
double r = ThreadLocalRandom.current().nextDouble(radius);
double theta = ThreadLocalRandom.current().nextDouble(Math.PI * 2);
double dx = r * Math.cos(theta);
double dz = r * Math.sin(theta);
Location soundLoc = new Location(world, base.getX() + dx, base.getY(), base.getZ() + dz);
List<Sound> sounds = getSounds();
Sound chosen = sounds.get(ThreadLocalRandom.current().nextInt(sounds.size()));
log.info("Playing sound {} at {} (for {})", chosen, soundLoc, target.getName());
world.playSound(soundLoc, chosen, 1f, 1.0f);
}
}

View File

@ -1,12 +1,9 @@
package com.alttd.playerutils.util;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material;
import org.bukkit.block.ShulkerBox;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.BlockStateMeta;
@ -22,30 +19,8 @@ public final class BookByteUtils {
}
// 65,000 bytes per book (below CoreProtect hard limit ~65,535)
@Getter
private static final int MAX_BOOK_BYTES = 30_000;
@Getter
private static final int BIG_BOOK_BYTES = 10_000;
public static int getBigBookBytes(HumanEntity humanEntity) {
return calcBookBytes(humanEntity, BIG_BOOK_BYTES);
}
public static int getMaxBookBytes(HumanEntity humanEntity) {
return calcBookBytes(humanEntity, MAX_BOOK_BYTES);
}
private static int calcBookBytes(HumanEntity humanEntity, int bookBytes) {
if (humanEntity.hasPermission("playerutils.bigbook.bypass")) {
return Integer.MAX_VALUE;
} else if (humanEntity.hasPermission("playerutils.bigbook.double")) {
return bookBytes * 2;
} else if (humanEntity.hasPermission("playerutils.bigbook.quadruple")) {
return bookBytes * 4;
} else {
return bookBytes;
}
}
public static final int MAX_BOOK_BYTES = 30_000;
public static final int BIG_BOOK_BYTES = 10_000;
public static boolean shouldCountForBookByteLimit(ItemStack stack) {
if (stack == null) {

View File

@ -2,10 +2,6 @@ name: PlayerUtils
version: '${version}'
main: com.alttd.playerutils.PlayerUtils
api-version: '1.20'
softdepend:
- PlotSquared
- LuckPerms
- Vault
commands:
playerutils:
description: Base command for player utils