Author SHA1 Message Date
stijn 6c66ac7cc8 Refactor April Fools' prank logic: replace single explosion sound with randomized sounds, add logging, and adjust sound radius. 2026-03-31 23:25:35 +02:00
stijn 592f58a89b Add April Fools' prank functionality with scheduled explosions and test command 2026-03-31 22:51:37 +02:00
stijn 57b5b8fe84 Refactor book byte enforcement by replacing BookByteChunkLimitListener with simplified BookByteLimitListener. Adjust byte limits and improve oversized book handling logic. 2026-03-27 20:46:09 +01:00
stijn e03c51198c Add BookByteChunkLimitListener and BookWriteEvent to enforce book byte limits and prevent chunk saturation 2026-03-27 18:51:43 +01:00
stijn bd7a46c283 Fix project name in settings.gradle.kts to PlayerUtils 2026-01-21 07:18:12 +01:00
stijn 4b1c2078eb Add BlockDispenseEvent listener to ensure armor stands placed through dispensers respect the armor stand chunk limit 2026-01-21 07:10:46 +01:00
stijn f7313f8f25 Add ReCountArmorStands command and handle armorstands being removed 2026-01-21 06:59:35 +01:00
stijn cb98c3761b Update PlayerJoin to handle attribute keys and improve attribute modification logic 2025-11-01 00:56:19 +01:00
stijn df347fde7f Add PlayerJoin event listener to modify player attributes on join and update project configuration 2025-10-31 01:45:54 +01:00
stijn ab162b5094 Refactor logging by replacing custom Logger with SLF4J and adapt all usages 2025-07-24 23:04:52 +02:00
stijn c66b30ff90 Update XPCheque to use calculateTotalExperiencePoints and setExperienceLevelAndProgress to fix exp reset bug 2025-07-22 19:27:12 +02:00
stijn 7625c57b8b Refine ghast speed message to display human-readable speed names 2025-07-20 04:44:45 +02:00
stijn 7f6d4c4b36 Add ghast speed command with configurable speed levels 2025-07-20 04:36:02 +02:00
stijn d6d7269fee Fix inverted spawn egg check in BlockBlockUseEvent 2025-07-17 23:12:47 +02:00
stijn e81d532178 Restrict usage of END_PORTAL_FRAME in BlockBlockUseEvent 2025-07-17 00:04:11 +02:00
stijn c016db5969 Update BlockBlockUseEvent to use MaterialTags for spawn egg checks 2025-07-16 23:56:42 +02:00
stijn 05fd3beb9c Add BlockBlockUseEvent to restrict usage of blocked items and blocks 2025-07-15 21:25:02 +02:00
auto f80645e2fe Switch to using cosmos 2025-06-20 23:14:41 +02:00
auto 785a64b7e6 Refactor Glow command to optimize logic order.
Reorganized the "off" condition block to execute after clearing existing glow teams.
2025-03-24 21:26:56 +01:00
auto 7cfdc39f75 Refactor interact event handling and add trial spawner logic.
Renamed the event handler for clarity and added logic to handle trial spawner interactions with cooldown feedback for players. Introduced functional interfaces for cleaner and reusable code when processing armor stand counts and trial spawner timers.
2025-03-22 20:57:59 +01:00
31 changed files with 1165 additions and 186 deletions
+7 -1
View File
@@ -1,6 +1,7 @@
plugins {
id("java")
id("maven-publish")
id("com.github.ben-manes.versions") version "0.52.0"
}
group = "com.alttd"
@@ -38,7 +39,12 @@ tasks {
}
dependencies {
compileOnly("com.alttd:Galaxy-API:1.21-R0.1-SNAPSHOT") {
compileOnly("com.alttd.cosmos:cosmos-api:1.21.10-R0.1-SNAPSHOT") {
isChanging = true
}
implementation("org.slf4j:slf4j-api:2.0.17")
compileOnly("org.projectlombok:lombok:1.18.38")
annotationProcessor("org.projectlombok:lombok:1.18.38")
}
+12 -2
View File
@@ -1,11 +1,21 @@
rootProject.name = "PlayerUtils"
val nexusUser = providers.gradleProperty("alttdSnapshotUsername").orNull ?: System.getenv("NEXUS_USERNAME")
val nexusPass = providers.gradleProperty("alttdSnapshotPassword").orNull ?: System.getenv("NEXUS_PASSWORD")
dependencyResolutionManagement {
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy
maven("'https://jitpack.io'") // Vault
maven {
url = uri("https://repo.alttd.com/repository/alttd-snapshot/")
credentials {
username = nexusUser
password = nexusPass
}
}
maven("https://repo.destro.xyz/snapshots")
maven("https://jitpack.io")
}
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
}
@@ -1,26 +1,30 @@
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.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 com.alttd.playerutils.util.Logger;
import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.concurrent.TimeUnit;
import com.alttd.playerutils.util.AprilFoolsPrank;
public final class PlayerUtils extends JavaPlugin {
private Logger logger;
private PlayerUtilsCommand playerUtilsCommand;
private AprilFoolsPrank aprilFoolsPrank;
@Override
public void onEnable() {
this.logger = new Logger(getLogger());
// initialize prank utility
aprilFoolsPrank = new AprilFoolsPrank(this);
registerCommands();
registerEvents();
reloadConfigs();
@@ -33,29 +37,43 @@ public final class PlayerUtils extends JavaPlugin {
}
private void registerCommands() {
playerUtilsCommand = new PlayerUtilsCommand(this, logger);
playerUtilsCommand = new PlayerUtilsCommand(this);
// add april fools test command
playerUtilsCommand.addSubCommand(new AprilFools(aprilFoolsPrank));
}
private void registerEvents() {
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new XpBottleEvent(this, logger), this);
pluginManager.registerEvents(new XpBottleEvent(this), this);
pluginManager.registerEvents(new TeleportEvent(), this);
pluginManager.registerEvents(new GoatHornEvent(logger), this);
pluginManager.registerEvents(new LimitArmorStands(this, logger), this);
pluginManager.registerEvents(new GoatHornEvent(), this);
pluginManager.registerEvents(new LimitArmorStands(this), this);
pluginManager.registerEvents(new BlockBlockUseEvent(), this);
pluginManager.registerEvents(new PlayerJoin(this), this);
pluginManager.registerEvents(new BookWriteEvent(), this);
pluginManager.registerEvents(new BookByteLimitListener(), this);
RotateBlockEvent rotateBlockEvent = new RotateBlockEvent(logger);
RotateBlockEvent rotateBlockEvent = new RotateBlockEvent();
pluginManager.registerEvents(rotateBlockEvent, this);
playerUtilsCommand.addSubCommand(new RotateBlock(rotateBlockEvent));
GhastSpeedEvent ghastSpeedEvent = new GhastSpeedEvent();
pluginManager.registerEvents(ghastSpeedEvent, this);
playerUtilsCommand.addSubCommand(new GhastSpeed(ghastSpeedEvent));
}
public void reloadConfigs() {
Config.reload(logger);
Messages.reload(logger);
KeyStorage.reload(logger);
Config.reload();
Messages.reload();
KeyStorage.reload();
}
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();
}
}
@@ -3,26 +3,26 @@ package com.alttd.playerutils.commands;
import com.alttd.playerutils.PlayerUtils;
import com.alttd.playerutils.commands.playerutils_subcommands.*;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.event_listeners.RotateBlockEvent;
import com.alttd.playerutils.util.Logger;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.command.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j @Getter
public class PlayerUtilsCommand implements CommandExecutor, TabExecutor {
private final List<SubCommand> subCommands;
public PlayerUtilsCommand(PlayerUtils playerUtils, Logger logger) {
public PlayerUtilsCommand(PlayerUtils playerUtils) {
PluginCommand command = playerUtils.getCommand("playerutils");
if (command == null) {
subCommands = null;
logger.severe("Unable to find playerutils command.");
log.error("Unable to find playerutils command.");
return;
}
command.setExecutor(this);
@@ -30,11 +30,12 @@ public class PlayerUtilsCommand implements CommandExecutor, TabExecutor {
command.setAliases(List.of("pu"));
subCommands = new ArrayList<>(List.of(
new Glow(logger),
new Glow(),
new XPCheque(playerUtils),
new XPCalc(),
new Reload(playerUtils),
new Key(logger))
new Key(),
new ReCountArmorStands(playerUtils))
);
}
@@ -85,10 +86,6 @@ public class PlayerUtilsCommand implements CommandExecutor, TabExecutor {
return res;
}
public List<SubCommand> getSubCommands() {
return subCommands;
}
private SubCommand getSubCommand(String cmdName) {
return subCommands.stream()
.filter(subCommand -> subCommand.getName().equals(cmdName))
@@ -0,0 +1,11 @@
package com.alttd.playerutils.commands.argument_parser;
import org.bukkit.command.CommandSender;
import java.util.Optional;
public interface ArgumentParser<T> {
Optional<T> parse(CommandSender commandSender, String argument);
}
@@ -0,0 +1,23 @@
package com.alttd.playerutils.commands.argument_parser;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.data_objects.GHAST_SPEED;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Optional;
public class GhastSpeedParser implements ArgumentParser<GHAST_SPEED> {
@Override
public Optional<GHAST_SPEED> parse(CommandSender commandSender, String speed) {
GHAST_SPEED ghastSpeed;
try {
ghastSpeed = GHAST_SPEED.valueOf(speed.toUpperCase());
} catch (IllegalArgumentException e) {
return Optional.empty();
}
return Optional.of(ghastSpeed);
}
}
@@ -0,0 +1,21 @@
package com.alttd.playerutils.commands.argument_parser;
import com.alttd.playerutils.config.Messages;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Optional;
public class OnlinePlayerParser implements ArgumentParser<Player> {
@Override
public Optional<Player> parse(CommandSender commandSender, String playerName) {
Player player = commandSender.getServer().getPlayer(playerName);
if (player == null || !player.isOnline()) {
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_NOT_FOUND, Placeholder.parsed("player", playerName));
return Optional.empty();
}
return Optional.of(player);
}
}
@@ -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).";
}
}
@@ -0,0 +1,90 @@
package com.alttd.playerutils.commands.playerutils_subcommands;
import com.alttd.playerutils.commands.SubCommand;
import com.alttd.playerutils.commands.argument_parser.GhastSpeedParser;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.data_objects.GHAST_SPEED;
import com.alttd.playerutils.event_listeners.GhastSpeedEvent;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.HappyGhast;
import org.bukkit.entity.Player;
import java.util.*;
public class GhastSpeed extends SubCommand {
private final static GhastSpeedParser GHAST_SPEED_PARSER = new GhastSpeedParser();
private final static int GHAST_SPEED_ARG = 1;
private final GhastSpeedEvent ghastSpeedEvent;
public GhastSpeed(GhastSpeedEvent ghastSpeedEvent) {
this.ghastSpeedEvent = ghastSpeedEvent;
}
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
if (args.length != 2) {
return false;
}
if (!(commandSender instanceof Player player)) {
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
return true;
}
Optional<GHAST_SPEED> parsedGhastSpeed = GHAST_SPEED_PARSER.parse(commandSender, args[GHAST_SPEED_ARG]);
if (parsedGhastSpeed.isEmpty()) {
return false;
}
GHAST_SPEED ghastSpeed = parsedGhastSpeed.get();
if (!(player.getVehicle() instanceof HappyGhast happyGhast)) {
commandSender.sendRichMessage(Messages.GHAST_SPEED.NOT_RIDING_A_GHAST);
return true;
}
AttributeInstance attribute = happyGhast.getAttribute(Attribute.FLYING_SPEED);
if (attribute == null) {
commandSender.sendRichMessage(Messages.GHAST_SPEED.FAILED_TO_SET_SPEED);
return true;
}
if (!commandSender.hasPermission(getPermission() + "." + ghastSpeed.name().toLowerCase())) {
commandSender.sendRichMessage(Messages.GENERIC.NO_PERMISSION,
Placeholder.parsed("permission", getPermission() + "." +
ghastSpeed.name().toLowerCase()));
return true;
}
double newSpeed = GHAST_SPEED.getSpeed(ghastSpeed);
ghastSpeedEvent.setNewSpeed(player.getUniqueId(), ghastSpeed);
attribute.setBaseValue(newSpeed);
commandSender.sendRichMessage(Messages.GHAST_SPEED.NEW_SPEED_SET_TO,
Placeholder.parsed("speed", ghastSpeed.name().toLowerCase().replace("_", " ")));
return true;
}
@Override
public String getName() {
return "ghastspeed";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
if (args.length == 2) {
return Arrays.stream(GHAST_SPEED.values())
.map(GHAST_SPEED::name)
.filter(name -> commandSender.hasPermission(
getPermission() + "." + name.toLowerCase())).toList();
}
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.GHAST_SPEED;
}
}
@@ -2,7 +2,7 @@ package com.alttd.playerutils.commands.playerutils_subcommands;
import com.alttd.playerutils.commands.SubCommand;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.util.Logger;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
@@ -19,13 +19,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class Glow extends SubCommand {
private final Logger logger;
public Glow(Logger logger) {
this.logger = logger;
}
@Slf4j public class Glow extends SubCommand {
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
@@ -41,17 +35,17 @@ public class Glow extends SubCommand {
Player player = playerFromArg.get();
if (args[1].equalsIgnoreCase("off")) {
turnOffGlow(commandSender, player, otherPlayer);
return true;
}
Scoreboard board = Bukkit.getScoreboardManager().getMainScoreboard();
board.getTeams().stream()
.filter(team -> team.getName().startsWith("Glow"))
.filter(team -> team.hasPlayer(player))
.forEach(team -> team.removePlayer(player));
if (args[1].equalsIgnoreCase("off")) {
turnOffGlow(commandSender, player, otherPlayer);
return true;
}
Optional<DyeColor> any = Arrays.stream(DyeColor.values()).filter(chatColor -> chatColor.name().equalsIgnoreCase(args[1])).findAny();
if (any.isEmpty()) {
commandSender.sendRichMessage(getHelpMessage());
@@ -97,6 +91,7 @@ public class Glow extends SubCommand {
private void turnOffGlow(CommandSender commandSender, Player player, boolean otherPlayer) {
player.sendRichMessage(Messages.GLOW.GLOW_OFF);
player.setGlowing(false);
if (otherPlayer) {
commandSender.sendRichMessage(Messages.GLOW.GLOW_OFF_FOR_PLAYER, Placeholder.component("player", player.name()));
}
@@ -105,7 +100,7 @@ public class Glow extends SubCommand {
private void turnOnGlow(CommandSender commandSender, Player player, Team team, DyeColor dyeColor, boolean otherPlayer) {
if (team.getScoreboard() == null) {
commandSender.sendRichMessage(Messages.GLOW.UNABLE_TO_GET_SCOREBOARD);
logger.warning("Unable to get scoreboard for team");
log.warn("Unable to get scoreboard for team");
return;
}
@@ -4,8 +4,8 @@ import com.alttd.playerutils.commands.SubCommand;
import com.alttd.playerutils.config.Config;
import com.alttd.playerutils.config.KeyStorage;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.util.Logger;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.bukkit.Bukkit;
@@ -15,13 +15,7 @@ import org.bukkit.entity.Player;
import java.util.List;
import java.util.UUID;
public class Key extends SubCommand {
private final Logger logger;
public Key(Logger logger) {
this.logger = logger;
}
@Slf4j public class Key extends SubCommand {
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
@@ -52,7 +46,7 @@ public class Key extends SubCommand {
}
crateMap.addTo(uuid, 1);
logger.info(String.format("Gave %s one key for %s", player.getName(), crate));
log.info("Gave {} one key for {}", player.getName(), crate);
commandSender.getServer().dispatchCommand(Bukkit.getConsoleSender(), String.format("crate give v %s 1 %s", crate, player.getName()));
if (keys + 1 == totalKeys) {
commandSender.sendRichMessage(Messages.KEY.GAVE_FINAL_KEY, TagResolver.resolver(
@@ -0,0 +1,75 @@
package com.alttd.playerutils.commands.playerutils_subcommands;
import com.alttd.playerutils.PlayerUtils;
import com.alttd.playerutils.commands.SubCommand;
import com.alttd.playerutils.config.Messages;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Chunk;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor
public class ReCountArmorStands extends SubCommand {
private final PlayerUtils playerUtils;
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
if (!(commandSender instanceof Player player)) {
return false;
}
Chunk chunk = player.getLocation().getChunk();
Optional<Integer> armorStandCount = recountArmorStands(chunk);
if (armorStandCount.isEmpty()) {
player.sendRichMessage(Messages.RECOUNT_ARMOR_STANDS.FAILED_COUNT);
return true;
}
player.sendRichMessage(Messages.RECOUNT_ARMOR_STANDS.SUCCESS, Placeholder.parsed("count", String.valueOf(armorStandCount.get())));
return true;
}
@Override
public String getName() {
return "recount_armor_stands";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.RECOUNT_ARMOR_STANDS;
}
private Optional<Integer> recountArmorStands(Chunk chunk) {
NamespacedKey namespacedKey = NamespacedKey.fromString("armor_stand_count", playerUtils);
if (namespacedKey == null) {
log.warn("Unable to retrieve name spaced key for armor stand count.");
return Optional.empty();
}
PersistentDataContainer persistentDataContainer = chunk.getPersistentDataContainer();
int armorStands = countArmorStands(chunk);
persistentDataContainer.set(namespacedKey, PersistentDataType.INTEGER, armorStands);
return Optional.of(armorStands);
}
private int countArmorStands(Chunk chunk) {
return (int) Arrays.stream(chunk.getEntities())
.filter(entity -> entity.getType().equals(EntityType.ARMOR_STAND))
.count();
}
}
@@ -64,7 +64,7 @@ public class XPCheque extends SubCommand {
return true;
}
int totalExperience = player.getTotalExperience();
int totalExperience = player.calculateTotalExperiencePoints();
if (totalExperience < (xpValue * amount)) {
commandSender.sendRichMessage(Messages.XP_CHEQUE.NOT_ENOUGH_XP, Placeholder.parsed("xp", String.valueOf(totalExperience)));
return true;
@@ -139,7 +139,7 @@ public class XPCheque extends SubCommand {
}
public void decreaseExperience(Player player, int xpToRemove) {
int totalExp = player.getTotalExperience();
int totalExp = player.calculateTotalExperiencePoints();
int newTotalExp = Math.max(totalExp - xpToRemove, 0);
int level = 0;
@@ -153,7 +153,7 @@ public class XPCheque extends SubCommand {
float progress = (float) newTotalExp / getExpToNext(level);
player.setTotalExperience(totalExp - xpToRemove);
player.setExperienceLevelAndProgress(totalExp - xpToRemove);
player.setLevel(level);
player.setExp(progress);
}
@@ -1,8 +1,8 @@
package com.alttd.playerutils.config;
import com.alttd.playerutils.PlayerUtils;
import com.alttd.playerutils.util.Logger;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -19,19 +19,16 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@SuppressWarnings({"unused", "SameParameterValue"})
@Slf4j @SuppressWarnings({"unused", "SameParameterValue"})
abstract class AbstractConfig {
File file;
YamlConfiguration yaml;
private static Logger logger = null;
AbstractConfig(PlayerUtils playerUtils, String filename, Logger logger) {
AbstractConfig.logger = logger;
AbstractConfig(PlayerUtils playerUtils, String filename) {
init(new File(playerUtils.getDataFolder(), filename), filename);
}
AbstractConfig(File file, String filename, Logger logger) {
AbstractConfig.logger = logger;
AbstractConfig(File file, String filename) {
init(new File(file.getPath() + File.separator + filename), filename);
}
@@ -41,10 +38,9 @@ abstract class AbstractConfig {
try {
yaml.load(file);
} catch (IOException ignore) {
} catch (InvalidConfigurationException ex) {
if (logger != null)
logger.severe(String.format("Could not load %s, please correct your syntax errors", filename));
throw new RuntimeException(ex);
} catch (InvalidConfigurationException e) {
log.error("Could not load {}, please correct your syntax errors", filename, e);
throw new RuntimeException(e);
}
yaml.options().copyDefaults(true);
}
@@ -59,10 +55,8 @@ abstract class AbstractConfig {
method.invoke(instance);
} catch (InvocationTargetException ex) {
throw new RuntimeException(ex.getCause());
} catch (Exception ex) {
if (logger != null)
logger.severe("Error invoking %.", method.toString());
ex.printStackTrace();
} catch (Exception e) {
log.error("Error invoking {}.", method, e);
}
}
}
@@ -75,10 +69,8 @@ abstract class AbstractConfig {
private void save() {
try {
yaml.save(file);
} catch (IOException ex) {
if (logger != null)
logger.severe("Could not save %.", file.toString());
ex.printStackTrace();
} catch (IOException e) {
log.error("Could not save {}.", file.toString(), e);
}
}
@@ -150,4 +142,4 @@ abstract class AbstractConfig {
ConfigurationSection getConfigurationSection(String path) {
return yaml.getConfigurationSection(path);
}
}
}
@@ -1,31 +1,28 @@
package com.alttd.playerutils.config;
import com.alttd.playerutils.util.Logger;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.configuration.ConfigurationSection;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class Config extends AbstractConfig{
@Slf4j public class Config extends AbstractConfig{
static Config config;
private Logger logger;
Config(Logger logger) {
Config() {
super(
new File(File.separator
+ "mnt" + File.separator
+ "configs" + File.separator
+ "PlayerUtils"),
"config.yml", logger);
this.logger = logger;
"config.yml");
}
public static void reload(Logger logger) {
logger.info("Reloading config");
config = new Config(logger);
public static void reload() {
log.info("Reloading config");
config = new Config();
config.readConfig(Config.class, null);
}
@@ -50,7 +47,7 @@ public class Config extends AbstractConfig{
CRATES.clear();
ConfigurationSection configurationSection = config.getConfigurationSection(prefix.substring(0, prefix.length() - 1));
if (configurationSection == null) {
config.logger.warning("No keys configured, adding default");
log.warn("No keys configured, adding default");
config.set(prefix, "dailyvotecrate", 0);
config.set(prefix, "weeklyvotecrate", 0);
config.set(prefix, "questcrate", 0);
@@ -73,7 +70,7 @@ public class Config extends AbstractConfig{
LIMIT.clear();
ConfigurationSection configurationSection = config.getConfigurationSection(prefix.substring(0, prefix.length() - 1));
if (configurationSection == null) {
config.logger.warning("No limits configured, adding default");
log.warn("No limits configured, adding default");
config.set(prefix, "default", 10);
}
Set<String> limits = configurationSection.getKeys(false);
@@ -82,4 +79,17 @@ public class Config extends AbstractConfig{
}
}
}
public static class LOCATOR_BAR {
private static final String prefix = "locator_bar.";
public static double WAYPOINT_RECEIVE_RANGE = 200;
public static double WAYPOINT_TRANSMIT_RANGE = 200;
@SuppressWarnings("unused")
private static void load() {
WAYPOINT_RECEIVE_RANGE = config.getDouble(prefix, "waypoint_receive_range", WAYPOINT_RECEIVE_RANGE);
WAYPOINT_TRANSMIT_RANGE = config.getDouble(prefix, "waypoint_transmit_range", WAYPOINT_TRANSMIT_RANGE);
}
}
}
@@ -1,7 +1,7 @@
package com.alttd.playerutils.config;
import com.alttd.playerutils.util.Logger;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.configuration.ConfigurationSection;
import java.io.File;
@@ -9,24 +9,23 @@ import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@Slf4j
public class KeyStorage extends AbstractConfig {
static KeyStorage config;
private final Logger logger;
public KeyStorage(Logger logger) {
public KeyStorage() {
super(
new File(File.separator
+ "mnt" + File.separator
+ "configs" + File.separator
+ "PlayerUtils"),
"key_storage.yml", logger);
this.logger = logger;
"key_storage.yml");
}
public static void reload(Logger logger) {
logger.info("Reloading key storage");
config = new KeyStorage(logger);
public static void reload() {
log.info("Reloading key storage");
config = new KeyStorage();
config.readConfig(KeyStorage.class, null);
}
@@ -43,13 +42,13 @@ public class KeyStorage extends AbstractConfig {
Object2IntOpenHashMap<UUID> count = new Object2IntOpenHashMap<>();
ConfigurationSection configurationSection = config.getConfigurationSection(prefix + crate);
if (configurationSection == null) {
config.logger.info(String.format("No section yet for crate %s", crate));
log.info("No section yet for crate {}", crate);
KEYS.put(crate, count);
continue;
}
List<UUID> uuids = configurationSection.getKeys(false).stream().map(UUID::fromString).toList();
if (uuids.isEmpty()) {
config.logger.info(String.format("No keys yet for crate %s", crate));
log.info("No keys yet for crate {}", crate);
KEYS.put(crate, count);
continue;
}
@@ -62,7 +61,7 @@ public class KeyStorage extends AbstractConfig {
}
public synchronized static void save() {
config.logger.info("Saving KeyStorage");
log.info("Saving KeyStorage");
KEYS.keySet()
.forEach(crate -> KEYS.get(crate)
.forEach((uuid, keys) -> config.set(prefix + crate + ".", uuid.toString(), keys)));
@@ -1,6 +1,5 @@
package com.alttd.playerutils.config;
import com.alttd.playerutils.util.Logger;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@@ -8,20 +7,18 @@ import java.util.List;
public class Messages extends AbstractConfig {
static Messages config;
private final Logger logger;
Messages(Logger logger) {
Messages() {
super(
new File(File.separator
+ "mnt" + File.separator
+ "configs" + File.separator
+ "PlayerUtils"),
"messages.yml", logger);
this.logger = logger;
"messages.yml");
}
public static void reload(Logger logger) {
config = new Messages(logger);
public static void reload() {
config = new Messages();
config.readConfig(Messages.class, null);
}
@@ -36,6 +33,8 @@ public class Messages extends AbstractConfig {
public static String RELOAD = "<green>Reload the configs for PlayerUtils: <gold>/pu reload</gold></green>";
public static String ROTATE_BLOCK = "<green>Enable rotating blocks with a blaze rod: <gold>/pu rotateblock</gold></green>";
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>";
@SuppressWarnings("unused")
private static void load() {
@@ -46,6 +45,8 @@ public class Messages extends AbstractConfig {
XP_CALC = config.getString(prefix, "xp-calc", XP_CALC);
RELOAD = config.getString(prefix, "reload", RELOAD);
ROTATE_BLOCK = config.getString(prefix, "rotate-block", ROTATE_BLOCK);
GHAST_SPEED = config.getString(prefix, "ghast-speed", GHAST_SPEED);
RECOUNT_ARMOR_STANDS = config.getString(prefix, "recount-armor-stands", RECOUNT_ARMOR_STANDS);
}
}
@@ -157,4 +158,43 @@ public class Messages extends AbstractConfig {
GAVE_FINAL_KEY = config.getString(prefix, "gave-final-key", GAVE_FINAL_KEY);
}
}
public static class GHAST_SPEED {
private static final String prefix = "pu-command.ghast-speed.";
public static String NOT_RIDING_A_GHAST = "<red>You are not riding a ghast</red>";
public static String FAILED_TO_SET_SPEED = "<red>Failed to set ghast speed</red>";
public static String NEW_SPEED_SET_TO = "<green>New speed set to <speed></green>";
@SuppressWarnings("unused")
private static void load() {
NOT_RIDING_A_GHAST = config.getString(prefix, "not-riding-a-ghast", NOT_RIDING_A_GHAST);
FAILED_TO_SET_SPEED = config.getString(prefix, "failed-to-set-speed", FAILED_TO_SET_SPEED);
NEW_SPEED_SET_TO = config.getString(prefix, "new-speed-set-to", NEW_SPEED_SET_TO);
}
}
public static class BLOCK_BLOCK_USE {
private static final String prefix = "block-block-use.";
public static String BLOCKED = "<red>You are not powerful enough!</red>";
@SuppressWarnings("unused")
private static void load() {
BLOCKED = config.getString(prefix, "blocked", BLOCKED);
}
}
public static class RECOUNT_ARMOR_STANDS {
private static final String prefix = "recount-armor-stands.";
public static String FAILED_COUNT = "<red>Unable to recount armor stands.</red>";
public static String SUCCESS = "<green>Recounted <count> armor stands and the count to chunk data.</green>";
@SuppressWarnings("unused")
private static void load() {
FAILED_COUNT = config.getString(prefix, "failed-count", FAILED_COUNT);
SUCCESS = config.getString(prefix, "success", SUCCESS);
}
}
}
@@ -0,0 +1,19 @@
package com.alttd.playerutils.data_objects;
public enum GHAST_SPEED {
SLOW,
NORMAL,
FAST,
VERY_FAST,
EXTREMELY_FAST;
public static double getSpeed(GHAST_SPEED ghastSpeed) {
return switch (ghastSpeed) {
case SLOW -> 0.025;
case NORMAL -> 0.05;
case FAST -> 0.075;
case VERY_FAST -> 0.1;
case EXTREMELY_FAST -> 0.125;
};
}
}
@@ -0,0 +1,65 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.config.Messages;
import com.destroystokyo.paper.MaterialTags;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockCanBuildEvent;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
public class BlockBlockUseEvent implements Listener {
@EventHandler
public void onBlockPlace(BlockCanBuildEvent event) {
Player player = event.getPlayer();
if (player != null && player.hasPermission("playerutils.block-block-use.bypass")) {
return;
}
if (isNotBlocked(event.getMaterial())) {
return;
}
event.setBuildable(false);
if (player != null) {
player.sendRichMessage(Messages.BLOCK_BLOCK_USE.BLOCKED);
}
}
@EventHandler
public void onItemUse(PlayerInteractEvent event) {
Player player = event.getPlayer();
if (player.hasPermission("playerutils.block-block-use.bypass")) {
return;
}
if (!event.getAction().isRightClick()) {
return;
}
ItemStack item = event.getItem();
if (item == null || isNotBlocked(item.getType())) {
return;
}
player.sendRichMessage(Messages.BLOCK_BLOCK_USE.BLOCKED);
event.setCancelled(true);
}
@EventHandler
public void onDispenserUse(BlockDispenseEvent event) {
if (isNotBlocked(event.getItem().getType())) {
return;
}
event.setCancelled(true);
}
public boolean isNotBlocked(Material type) {
return !type.equals(Material.BEDROCK)
&& !type.equals(Material.SPAWNER)
&& !type.equals(Material.BARRIER)
&& !type.equals(Material.END_PORTAL_FRAME)
&& !MaterialTags.SPAWN_EGGS.isTagged(type);
}
}
@@ -0,0 +1,103 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.util.BookByteUtils;
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 org.bukkit.Bukkit;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Item;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.inventory.ItemStack;
@Slf4j
public class BookByteLimitListener implements Listener {
private boolean isOversizedBook(ItemStack stack) {
boolean isOversizedBook = BookByteUtils.shouldCountForBookByteLimit(stack)
&& BookByteUtils.computeBytes(stack) > BookByteUtils.MAX_BOOK_BYTES;
if (isOversizedBook) {
log.warn("Player tried to drop an oversized book");
Component message = MiniMessage.miniMessage().deserialize(
"<red>Player tried to drop an oversized book</red>");
Bukkit.broadcast(message, "staffutils.patrol");
}
return isOversizedBook;
}
private boolean isOversizedBook(ItemStack stack, HumanEntity humanEntity) {
boolean isOversizedBook = BookByteUtils.shouldCountForBookByteLimit(stack)
&& 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(
"<red>Player <player> tried to drop an oversized book</red>",
Placeholder.unparsed("player", humanEntity.getName()));
Bukkit.broadcast(message, "staffutils.patrol");
}
return isOversizedBook;
}
@EventHandler
public void onItemSpawn(ItemSpawnEvent event) {
Item item = event.getEntity();
if (isOversizedBook(item.getItemStack())) {
event.setCancelled(true);
}
}
@EventHandler
public void onPlayerDrop(PlayerDropItemEvent event) {
if (isOversizedBook(event.getItemDrop().getItemStack(), event.getPlayer())) {
event.setCancelled(true);
}
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
InventoryAction action = event.getAction();
switch (action) {
case PLACE_ALL, PLACE_ONE, PLACE_SOME, SWAP_WITH_CURSOR -> {
if (isOversizedBook(event.getCursor(), event.getWhoClicked())) {
event.setCancelled(true);
}
}
case MOVE_TO_OTHER_INVENTORY -> {
if (isOversizedBook(event.getCurrentItem(), event.getWhoClicked())) {
event.setCancelled(true);
}
}
case HOTBAR_SWAP -> {
ItemStack hotbarItem = event.getWhoClicked().getInventory().getItem(event.getHotbarButton());
if (isOversizedBook(hotbarItem, event.getWhoClicked())) {
event.setCancelled(true);
}
}
default -> {
}
}
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
if (isOversizedBook(event.getOldCursor(), event.getWhoClicked())) {
event.setCancelled(true);
}
}
@EventHandler
public void onInventoryMoveItem(InventoryMoveItemEvent event) {
if (isOversizedBook(event.getItem())) {
event.setCancelled(true);
}
}
}
@@ -0,0 +1,43 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.util.BookByteUtils;
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 org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerEditBookEvent;
import org.bukkit.inventory.meta.BookMeta;
@Slf4j
public class BookWriteEvent implements Listener {
@EventHandler
public void onPlayerEditBook(PlayerEditBookEvent event) {
Player player = event.getPlayer();
BookMeta meta = event.getNewBookMeta();
int totalBytes = BookByteUtils.computeBytes(meta);
if (totalBytes > BookByteUtils.MAX_BOOK_BYTES) {
log.warn("Player {} [{}] tried to write a book with {} bytes",
player.getName(), player.getUniqueId(), totalBytes);
event.setCancelled(true);
Component message = MiniMessage.miniMessage().deserialize(
"<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.BIG_BOOK_BYTES) {
log.warn("Player {} [{}] wrote a large book with {} bytes",
player.getName(), player.getUniqueId(), totalBytes);
Component message = MiniMessage.miniMessage().deserialize(
"<red>Player <player> wrote a book with <bytes> bytes</red>",
Placeholder.unparsed("player", player.getName()), Placeholder.parsed("bytes", String.valueOf(totalBytes)));
Bukkit.broadcast(message, "staffutils.patrol");
}
}
}
@@ -0,0 +1,71 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.data_objects.GHAST_SPEED;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Entity;
import org.bukkit.entity.HappyGhast;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDismountEvent;
import org.bukkit.event.entity.EntityMountEvent;
import java.util.HashMap;
import java.util.UUID;
@Slf4j
public class GhastSpeedEvent implements Listener {
private final HashMap<UUID, GHAST_SPEED> lastSetSpeed = new HashMap<>();
public GhastSpeedEvent() {
}
@EventHandler
public void onEntityMount(EntityMountEvent event) {
if (!(event.getEntity() instanceof Player player)) {
return;
}
Entity mount = event.getMount();
if (!(mount instanceof HappyGhast happyGhast)) {
return;
}
GHAST_SPEED ghastSpeed = lastSetSpeed.get(player.getUniqueId());
if (ghastSpeed == null) {
return;
}
AttributeInstance attribute = happyGhast.getAttribute(Attribute.FLYING_SPEED);
if (attribute == null) {
return;
}
attribute.setBaseValue(GHAST_SPEED.getSpeed(ghastSpeed));
}
@EventHandler
public void onEntityDismount(EntityDismountEvent event) {
if (!(event.getDismounted() instanceof HappyGhast happyGhast)) {
return;
}
if (happyGhast.getPassengers().size() >= 2) {
return;
}
AttributeInstance attribute = happyGhast.getAttribute(Attribute.FLYING_SPEED);
if (attribute == null) {
return;
}
attribute.setBaseValue(GHAST_SPEED.getSpeed(GHAST_SPEED.NORMAL));
}
public void setNewSpeed(UUID uuid, GHAST_SPEED speed) {
lastSetSpeed.put(uuid, speed);
}
}
@@ -1,6 +1,6 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.util.Logger;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@@ -11,13 +11,7 @@ import org.bukkit.inventory.ItemStack;
import java.util.concurrent.TimeUnit;
public class GoatHornEvent implements Listener {
private final Logger logger;
public GoatHornEvent(Logger logger) {
this.logger = logger;
}
@Slf4j public class GoatHornEvent implements Listener {
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
@@ -41,11 +35,10 @@ public class GoatHornEvent implements Listener {
if (player.getLocation().distance(spawn) > 250) {
logger.info(String.format("Player %s with uuid %s used a goat horn", player.getName(), player.getUniqueId()));
log.info("Player {} with uuid {} used a goat horn", player.getName(), player.getUniqueId());
return;
}
logger.info(String.format("Player %s with uuid %s used a goat horn in spawn", player.getName(), player.getUniqueId()));
log.info("Player {} with uuid {} used a goat horn in spawn", player.getName(), player.getUniqueId());
player.setCooldown(Material.GOAT_HORN, (int) TimeUnit.MINUTES.toSeconds(5) * 20);
}
@@ -2,31 +2,40 @@ package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.PlayerUtils;
import com.alttd.playerutils.config.Config;
import com.alttd.playerutils.util.Logger;
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.Chunk;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Block;
import org.bukkit.block.Dispenser;
import org.bukkit.block.TrialSpawner;
import org.bukkit.block.data.Directional;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
public class LimitArmorStands implements Listener {
@Slf4j public class LimitArmorStands implements Listener {
private final PlayerUtils playerUtils;
private final Logger logger;
public LimitArmorStands(PlayerUtils playerUtils, Logger logger) {
public LimitArmorStands(PlayerUtils playerUtils) {
this.playerUtils = playerUtils;
this.logger = logger;
}
@FunctionalInterface
@@ -35,8 +44,13 @@ public class LimitArmorStands implements Listener {
@NotNull PersistentDataContainer persistentDataContainer);
}
@FunctionalInterface
private interface TrialSpawnerTimerConsumer {
void apply(int ticks, @NotNull Player player);
}
@EventHandler
public void onArmorStandSpawn(PlayerInteractEvent event) {
public void onPlayerInteractEvent(PlayerInteractEvent event) {
handleArmorStandPlacing(event, (armorStandCount, player, namespacedKey, persistentDataContainer) -> {
int max = 0;
for (String permission : Config.ARMOR_STAND_LIMIT.LIMIT.keySet()) {
@@ -52,6 +66,77 @@ public class LimitArmorStands implements Listener {
}
persistentDataContainer.set(namespacedKey, PersistentDataType.INTEGER, ++armorStandCount);
});
if (event.getPlayer().hasPermission("pu.trial_spawner.cooldown")) {
handleRightClickTrialSpawner(event, (ticks, player) -> {
MiniMessage miniMessage = MiniMessage.miniMessage();
Title title = Title.title(Component.empty(),
miniMessage.deserialize("<green>Cooldown <red><seconds></red></green>",
Placeholder.parsed("seconds", String.valueOf(ticks / 20))));
player.showTitle(title);
});
}
}
@EventHandler
public void onPlayerDamageEntity(EntityDamageByEntityEvent event) {
if (!(event.getDamager() instanceof Player player)) {
return;
}
if (!(event.getEntity() instanceof ArmorStand armorStand)) {
return;
}
boolean willBreak = armorStand.getHealth() - event.getFinalDamage() <= 0.0;
if (!willBreak) {
return;
}
Optional<ChunkContainerAndNamespacedKey> optionalResult = getGetChunkContainerAndNamespacedKey(armorStand.getLocation().getChunk());
if (optionalResult.isEmpty()) {
player.sendRichMessage("<red>Something went wrong while checking the armor stand count. " +
"You will not be able to place this until this is fixed. Please contact a staff member</red>");
event.setCancelled(true);
return;
}
ChunkContainerAndNamespacedKey result = optionalResult.get();
int newArmorStandCount = Math.max(0, result.armorStandCount() - 1);
result.persistentDataContainer().set(result.namespacedKey(), PersistentDataType.INTEGER, newArmorStandCount);
}
private record ChunkContainerAndNamespacedKey(NamespacedKey namespacedKey, PersistentDataContainer persistentDataContainer, int armorStandCount) {
}
@EventHandler
public void onBlockDispense(BlockDispenseEvent event) {
if (!event.getItem().getType().equals(Material.ARMOR_STAND)) {
return;
}
Block block = event.getBlock();
if (!(block.getState() instanceof Dispenser)) {
return;
}
if (!(block.getBlockData() instanceof Directional directional)) {
return;
}
Chunk targetChunk = block.getRelative(directional.getFacing()).getChunk();
Optional<ChunkContainerAndNamespacedKey> optional = getGetChunkContainerAndNamespacedKey(targetChunk);
if (optional.isEmpty()) {
event.setCancelled(true);
return;
}
ChunkContainerAndNamespacedKey result = optional.get();
Optional<Integer> optionalLimit = Config.ARMOR_STAND_LIMIT.LIMIT.values().stream().min(Integer::compareTo);
if (optionalLimit.isEmpty()) {
log.error("Unable to find a valid limit for armor stands");
event.setCancelled(true);
return;
}
if (result.armorStandCount() >= optionalLimit.get()) {
event.setCancelled(true);
return;
}
result.persistentDataContainer().set(result.namespacedKey(), PersistentDataType.INTEGER, result.armorStandCount() + 1);
}
private void handleArmorStandPlacing(PlayerInteractEvent event, ArmorStandCountConsumer consumer) {
@@ -64,15 +149,24 @@ public class LimitArmorStands implements Listener {
if (event.getClickedBlock() == null) {
return;
}
Player player = event.getPlayer();;
Player player = event.getPlayer();
Chunk chunk = event.getClickedBlock().getChunk();
Optional<ChunkContainerAndNamespacedKey> getChunkContainerAndNamespacedKey = getGetChunkContainerAndNamespacedKey(chunk);
if (getChunkContainerAndNamespacedKey.isEmpty()) {
player.sendRichMessage("<red>Something went wrong while checking the armor stand count. " +
"You will not be able to place this until this is fixed. Please contact a staff member</red>");
event.setCancelled(true);
return;
}
ChunkContainerAndNamespacedKey result = getChunkContainerAndNamespacedKey.get();
consumer.apply(result.armorStandCount(), event.getPlayer(), result.namespacedKey(), result.persistentDataContainer());
}
private Optional<LimitArmorStands.ChunkContainerAndNamespacedKey> getGetChunkContainerAndNamespacedKey(Chunk chunk) {
NamespacedKey namespacedKey = NamespacedKey.fromString("armor_stand_count", playerUtils);
if (namespacedKey == null) {
event.setCancelled(true);
logger.warning("Unable to retrieve name spaced key for armor stand count.");
player.sendRichMessage("<red>Something went wrong while checking the armor stand count. " +
"You will not be able to place this until this is fixed. Please contact a staff member</red>");
return;
log.warn("Unable to retrieve name spaced key for armor stand count.");
return Optional.empty();
}
PersistentDataContainer persistentDataContainer = chunk.getPersistentDataContainer();
if (!persistentDataContainer.has(namespacedKey, PersistentDataType.INTEGER)) {
@@ -80,13 +174,39 @@ public class LimitArmorStands implements Listener {
}
Integer armorStandCount = persistentDataContainer.get(namespacedKey, PersistentDataType.INTEGER);
if (armorStandCount == null) {
event.setCancelled(true);
logger.warning("Unable to retrieve armor stand count.");
player.sendRichMessage("<red>Something went wrong while checking the armor stand count. " +
"You will not be able to place this until this is fixed. Please contact a staff member</red>");
return Optional.empty();
}
return Optional.of(new ChunkContainerAndNamespacedKey(namespacedKey, persistentDataContainer, armorStandCount));
}
private void handleRightClickTrialSpawner(PlayerInteractEvent event, TrialSpawnerTimerConsumer consumer) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
consumer.apply(armorStandCount, event.getPlayer(), namespacedKey, persistentDataContainer);
if (!event.getMaterial().equals(Material.AIR)) {
return;
}
if (event.getClickedBlock() == null) {
return;
}
Block block = event.getClickedBlock();
if (!block.getType().equals(Material.TRIAL_SPAWNER)) {
return;
}
if (!(block instanceof TrialSpawner trialSpawner)) {
return;
}
if (!(trialSpawner.getBlockData() instanceof org.bukkit.block.data.type.TrialSpawner trialSpawnerData)) {
return;
}
Player player = event.getPlayer();
if (!trialSpawnerData.getTrialSpawnerState().equals(org.bukkit.block.data.type.TrialSpawner.State.INACTIVE)) {
consumer.apply(0, player);
return;
}
consumer.apply(trialSpawner.getCooldownLength(), player);
}
private int countArmorStands(Chunk chunk) {
@@ -0,0 +1,66 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.config.Config;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.NamespacedKey;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
@Slf4j
public class PlayerJoin implements Listener {
private final NamespacedKey WAYPOINT_RECEIVE_KEY;
private final NamespacedKey WAYPOINT_TRANSMIT_KEY;
public PlayerJoin(JavaPlugin plugin) {
this.WAYPOINT_RECEIVE_KEY = new NamespacedKey(plugin, "waypoint_receive_modifier");
this.WAYPOINT_TRANSMIT_KEY = new NamespacedKey(plugin, "waypoint_transmit_modifier");
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
setModifiedAttribute(player, Attribute.WAYPOINT_RECEIVE_RANGE, Config.LOCATOR_BAR.WAYPOINT_RECEIVE_RANGE, WAYPOINT_RECEIVE_KEY);
setModifiedAttribute(player, Attribute.WAYPOINT_TRANSMIT_RANGE, Config.LOCATOR_BAR.WAYPOINT_TRANSMIT_RANGE, WAYPOINT_TRANSMIT_KEY);
}
private void setModifiedAttribute(Player player, Attribute attribute, double configValue, NamespacedKey key) {
AttributeInstance attributeInstance = player.getAttribute(attribute);
if (attributeInstance == null) {
log.error("Unable to retrieve attribute instance for player {}.", player.getName());
return;
}
for (AttributeModifier modifier : attributeInstance.getModifiers()) {
attributeInstance.removeModifier(modifier);
}
double attributeDelta = configValue - attributeInstance.getBaseValue();
if (attributeDelta != 0) {
AttributeModifier attributeModifier = new AttributeModifier(
key,
attributeDelta,
AttributeModifier.Operation.ADD_NUMBER
);
attributeInstance.addTransientModifier(attributeModifier);
}
double actualValue = attributeInstance.getValue();
if (Math.abs(actualValue - configValue) > 0.01) {
log.warn("Failed to set attribute {} to {} for {}, actual value is {}.",
attribute, configValue, player.getName(), actualValue);
} else {
log.info("Set attribute {} for {} to {}.", attribute, player.getName(), configValue);
}
}
}
@@ -1,6 +1,6 @@
package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.util.Logger;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Axis;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@@ -21,17 +21,12 @@ import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class RotateBlockEvent implements Listener {
private final HashSet<UUID> rotateEnabled = new HashSet<>();
private final Logger logger;
private static final List<BlockFace> VALID_FOUR_STATES = List.of(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST);
public RotateBlockEvent(Logger logger) {
this.logger = logger;
}
public synchronized boolean toggleRotate(UUID uuid) {
if (rotateEnabled.contains(uuid)) {
rotateEnabled.remove(uuid);
@@ -61,7 +56,8 @@ public class RotateBlockEvent implements Listener {
return;
Material type = block.getType();
logger.debug(String.format("Material %s with action %s", type, event.getAction().isLeftClick() ? "left click" : "right click"));
log.debug("Material {} with action {}", type, event.getAction().isLeftClick() ? "left " +
"click" : "right click");
if (type.equals(Material.IRON_TRAPDOOR) && event.getAction().isLeftClick()) {
event.setCancelled(true);
toggleTrapDoor(block, player);
@@ -0,0 +1,18 @@
package com.alttd.playerutils.event_listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityTameEvent;
import java.util.UUID;
public class VanillaPetTameEvent implements Listener {
@EventHandler
public void onEntityTame(EntityTameEvent event) {
UUID uniqueId = event.getOwner().getUniqueId();
}
}
@@ -2,7 +2,7 @@ package com.alttd.playerutils.event_listeners;
import com.alttd.playerutils.PlayerUtils;
import com.alttd.playerutils.config.Messages;
import com.alttd.playerutils.util.Logger;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
@@ -25,15 +25,13 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class XpBottleEvent implements Listener {
@Slf4j public class XpBottleEvent implements Listener {
private final PlayerUtils playerUtils;
private final Logger logger;
private final MiniMessage miniMessage = MiniMessage.miniMessage();
public XpBottleEvent(PlayerUtils playerUtils, Logger logger) {
public XpBottleEvent(PlayerUtils playerUtils) {
this.playerUtils = playerUtils;
this.logger = logger;
}
@EventHandler
@@ -42,7 +40,7 @@ public class XpBottleEvent implements Listener {
PersistentDataContainer persistentDataContainer = item.getItemMeta().getPersistentDataContainer();
NamespacedKey customXp = NamespacedKey.fromString("custom_xp", playerUtils);
if (customXp == null) {
logger.warning("Unable to retrieve name spaced key.");
log.warn("Unable to retrieve name spaced key.");
return;
}
Integer integer = persistentDataContainer.get(customXp, PersistentDataType.INTEGER);
@@ -83,7 +81,7 @@ public class XpBottleEvent implements Listener {
}
for (Map.Entry<CookingRecipe<?>, Integer> entry : recipesUsed.entrySet()) {
exp += entry.getKey().getExperience() * entry.getValue();
exp += (int) (entry.getKey().getExperience() * entry.getValue());
}
Optional<ItemStack> optionalItemStack = getExpBottleItem(player, exp);
@@ -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);
}
}
@@ -0,0 +1,106 @@
package com.alttd.playerutils.util;
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.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.BlockStateMeta;
import java.nio.charset.StandardCharsets;
/**
* Utility to compute the UTF-8 byte size of a written book's contents.
*/
public final class BookByteUtils {
private BookByteUtils() {
}
// 65,000 bytes per book (below CoreProtect hard limit ~65,535)
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) {
return false;
}
Material type = stack.getType();
if (type == Material.WRITTEN_BOOK || type == Material.WRITABLE_BOOK) {
return true;
}
if (stack.getItemMeta() instanceof BookMeta) {
return true;
}
if (!(stack.getItemMeta() instanceof BlockStateMeta bsm) || !(bsm.getBlockState() instanceof ShulkerBox shulker)) {
return false;
}
for (ItemStack content : shulker.getInventory().getContents()) {
if (content == null) {
continue;
}
Material contentType = content.getType();
if (contentType == Material.WRITTEN_BOOK || contentType == Material.WRITABLE_BOOK) {
return true;
}
if (content.getItemMeta() instanceof BookMeta) {
return true;
}
}
return false;
}
/**
* Compute the number of bytes used by the provided BookMeta.
*/
public static int computeBytes(BookMeta meta) {
if (meta == null) {
return 0;
}
int totalBytes = 0;
String title = meta.getTitle();
if (title != null) {
totalBytes += title.getBytes(StandardCharsets.UTF_8).length;
}
for (Component page : meta.pages()) {
if (page == null) {
continue;
}
String pageString = MiniMessage.miniMessage().serialize(page);
if (pageString.isEmpty()) {
continue;
}
totalBytes += pageString.getBytes(StandardCharsets.UTF_8).length;
}
return totalBytes;
}
/**
* Compute the number of bytes used by a book item stack. If the item is not a written book, returns 0.
*/
public static int computeBytes(ItemStack stack) {
if (stack == null) {
return 0;
}
// Direct written book
if (stack.getItemMeta() instanceof BookMeta meta) {
int perBook = computeBytes(meta);
return perBook * Math.max(1, stack.getAmount());
}
// Shulker box: sum bytes of contained written books
if (stack.getItemMeta() instanceof BlockStateMeta bsm && bsm.getBlockState() instanceof ShulkerBox shulker) {
int total = 0;
for (ItemStack content : shulker.getInventory().getContents()) {
if (content == null) {
continue;
}
if (content.getItemMeta() instanceof BookMeta bookMeta) {
total += computeBytes(bookMeta) * Math.max(1, content.getAmount());
}
}
return total;
}
return 0;
}
}
@@ -1,42 +0,0 @@
package com.alttd.playerutils.util;
import com.alttd.playerutils.config.Config;
public class Logger {
private final java.util.logging.Logger logger;
static private final String RESET = "\u001B[0m";
static private final String GREEN = "\u001B[32m";
static private final String TEAL = "\u001B[36m";
public Logger(java.util.logging.Logger logger) {
this.logger = logger;
}
public void debug(String debug, String... variables) {
if (!Config.SETTINGS.DEBUG)
return;
logger.info(TEAL + replace(debug, variables) + RESET);
}
public void info(String info, String... variables) {
logger.info(GREEN + replace(info, variables) + RESET);
}
public void warning(String warning, String... variables) {
if (!Config.SETTINGS.WARNINGS)
return;
logger.warning(replace(warning, variables));
}
public void severe(String severe, String... variables) {
logger.severe(replace(severe, variables));
}
private String replace(String text, String... variables) {
for (String variable : variables) {
text = text.replaceFirst("%", variable);
}
return text;
}
}