Compare commits

..
13 Commits
19 changed files with 1041 additions and 46 deletions
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
+13
View File
@@ -2,6 +2,7 @@ plugins {
id("java") id("java")
id("maven-publish") id("maven-publish")
id("com.github.ben-manes.versions") version "0.52.0" id("com.github.ben-manes.versions") version "0.52.0"
id("com.github.johnrengelman.shadow") version "8.1.1"
} }
group = "com.alttd.easter" group = "com.alttd.easter"
@@ -29,6 +30,14 @@ tasks {
archiveFileName.set("${rootProject.name}.jar") archiveFileName.set("${rootProject.name}.jar")
} }
named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
archiveFileName.set("${rootProject.name}.jar")
mergeServiceFiles()
}
build {
dependsOn("shadowJar")
}
processResources { processResources {
filteringCharset = Charsets.UTF_8.name() filteringCharset = Charsets.UTF_8.name()
duplicatesStrategy = DuplicatesStrategy.INCLUDE duplicatesStrategy = DuplicatesStrategy.INCLUDE
@@ -43,6 +52,10 @@ dependencies {
isChanging = true isChanging = true
} }
implementation("com.alttd.inventory_gui:InventoryGUI:1.1.5-20260201.231743-1") {
isChanging = true
}
implementation("org.slf4j:slf4j-api:2.0.17") implementation("org.slf4j:slf4j-api:2.0.17")
compileOnly("org.projectlombok:lombok:1.18.38") compileOnly("org.projectlombok:lombok:1.18.38")
+21 -9
View File
@@ -3,22 +3,34 @@ package com.alttd.easter;
import com.alttd.easter.commands.Command; import com.alttd.easter.commands.Command;
import com.alttd.easter.config.Config; import com.alttd.easter.config.Config;
import com.alttd.easter.config.Messages; import com.alttd.easter.config.Messages;
import com.alttd.easter.data.DataManager;
import com.alttd.easter.gui.TurnInGuiManager;
import com.alttd.easter.listeners.DeathListener;
import com.alttd.easter.npc.RabbitNpcManager;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public final class Easter extends JavaPlugin { public final class Easter extends JavaPlugin {
private DataManager dataManager;
private RabbitNpcManager rabbitNpcManager;
private TurnInGuiManager turnInGuiManager;
@Override @Override
public void onEnable() { public void onEnable() {
reloadConfigs();
this.dataManager = new DataManager(this);
this.turnInGuiManager = new TurnInGuiManager(dataManager);
this.rabbitNpcManager = new RabbitNpcManager(this, turnInGuiManager, dataManager);
registerCommands(); registerCommands();
registerEvents(); registerEvents();
reloadConfigs();
registerSchedulers();
} }
@Override @Override
public void onDisable() { public void onDisable() {
//TODO save data if (dataManager != null) {
dataManager.save();
}
} }
private void registerCommands() { private void registerCommands() {
@@ -27,16 +39,16 @@ public final class Easter extends JavaPlugin {
private void registerEvents() { private void registerEvents() {
PluginManager pluginManager = getServer().getPluginManager(); PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new DeathListener(), this);
//TODO register events pluginManager.registerEvents(rabbitNpcManager, this);
pluginManager.registerEvents(turnInGuiManager, this);
} }
public void reloadConfigs() { public void reloadConfigs() {
Config.reload(); Config.reload();
Messages.reload(); Messages.reload();
} if (rabbitNpcManager != null) {
rabbitNpcManager.reload();
private void registerSchedulers() { }
//TODO register schedulers
} }
} }
@@ -1,7 +1,10 @@
package com.alttd.easter.commands; package com.alttd.easter.commands;
import com.alttd.easter.Easter; import com.alttd.easter.Easter;
import com.alttd.easter.commands.subcommands.*; import com.alttd.easter.commands.subcommands.Reload;
import com.alttd.easter.commands.subcommands.SetPrize;
import com.alttd.easter.commands.subcommands.SpawnEggs;
import com.alttd.easter.commands.subcommands.GetPrizes;
import com.alttd.easter.config.Messages; import com.alttd.easter.config.Messages;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -18,7 +21,8 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuppressWarnings("ClassCanBeRecord") @SuppressWarnings("ClassCanBeRecord")
@Slf4j @Getter @Slf4j
@Getter
public class Command implements CommandExecutor, TabExecutor { public class Command implements CommandExecutor, TabExecutor {
private final List<SubCommand> subCommands; private final List<SubCommand> subCommands;
@@ -31,10 +35,12 @@ public class Command implements CommandExecutor, TabExecutor {
} }
command.setExecutor(this); command.setExecutor(this);
command.setTabCompleter(this); command.setTabCompleter(this);
command.setAliases(List.of("pu"));
subCommands = List.of( subCommands = List.of(
new Reload(easter) new Reload(easter),
new SetPrize(),
new GetPrizes(),
new SpawnEggs()
); );
} }
@@ -49,8 +55,9 @@ public class Command implements CommandExecutor, TabExecutor {
} }
SubCommand subCommand = getSubCommand(args[0]); SubCommand subCommand = getSubCommand(args[0]);
if (subCommand == null) if (subCommand == null) {
return false; return false;
}
if (!commandSender.hasPermission(subCommand.getPermission())) { if (!commandSender.hasPermission(subCommand.getPermission())) {
commandSender.sendRichMessage(Messages.GENERIC.NO_PERMISSION, Placeholder.parsed("permission", subCommand.getPermission())); commandSender.sendRichMessage(Messages.GENERIC.NO_PERMISSION, Placeholder.parsed("permission", subCommand.getPermission()));
@@ -77,11 +84,12 @@ public class Command implements CommandExecutor, TabExecutor {
); );
} else { } else {
SubCommand subCommand = getSubCommand(args[0]); SubCommand subCommand = getSubCommand(args[0]);
if (subCommand != null && commandSender.hasPermission(subCommand.getPermission())) if (subCommand != null && commandSender.hasPermission(subCommand.getPermission())) {
res.addAll(subCommand.getTabComplete(commandSender, args).stream() res.addAll(subCommand.getTabComplete(commandSender, args).stream()
.filter(str -> str.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) .filter(str -> str.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.toList()); .toList());
} }
}
return res; return res;
} }
@@ -6,14 +6,15 @@ import java.util.List;
public abstract class SubCommand { public abstract class SubCommand {
public SubCommand() {} public SubCommand() {
}
public abstract boolean onCommand(CommandSender commandSender, String[] args); public abstract boolean onCommand(CommandSender commandSender, String[] args);
public abstract String getName(); public abstract String getName();
public String getPermission() { public String getPermission() {
return "playerutils." + getName(); return "easter." + getName();
} }
public abstract List<String> getTabComplete(CommandSender commandSender, String[] args); public abstract List<String> getTabComplete(CommandSender commandSender, String[] args);
@@ -0,0 +1,61 @@
package com.alttd.easter.commands.subcommands;
import com.alttd.easter.commands.SubCommand;
import com.alttd.easter.config.Config;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.command.CommandSender;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
public class GetPrizes extends SubCommand {
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
List<ItemStack> list = Config.PRIZES.LIST;
if (list.isEmpty()) {
commandSender.sendRichMessage("<red>No prizes are configured.</red>");
return true;
}
commandSender.sendRichMessage("<gold>Configured prizes (index : material - name)</gold>");
for (int i = 0; i < list.size(); i++) {
ItemStack item = list.get(i);
String material = item.getType().name();
String name = material;
ItemMeta meta = item.getItemMeta();
if (meta != null && meta.hasDisplayName()) {
Component component = meta.displayName();
if (component != null) {
name = PlainTextComponentSerializer.plainText().serialize(component);
}
}
commandSender.sendRichMessage("<gray><index></gray> : <yellow><material></yellow> - <green><name></green>",
TagResolver.resolver(
Placeholder.parsed("index", String.valueOf(i)),
Placeholder.parsed("material", material),
Placeholder.parsed("name", name)
));
}
return true;
}
@Override
public String getName() {
return "getprizes";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return new ArrayList<>();
}
@Override
public String getHelpMessage() {
return "<green>List configured prizes with index: <gold>/easter getprizes</gold></green>";
}
}
@@ -0,0 +1,68 @@
package com.alttd.easter.commands.subcommands;
import com.alttd.easter.commands.SubCommand;
import com.alttd.easter.config.Config;
import com.alttd.easter.config.Messages;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
public class SetPrize extends SubCommand {
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
if (!(commandSender instanceof Player player)) {
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
return true;
}
ItemStack item = player.getInventory().getItemInMainHand();
if (item.getType().isAir()) {
player.sendRichMessage(Messages.SETPRIZE.EMPTY_HAND);
return true;
}
// args[0] is the subcommand name. Optional args[1] may be index.
if (args.length >= 2) {
try {
int index = Integer.parseInt(args[1]);
if (index < 0 || index > Config.PRIZES.LIST.size()) {
player.sendRichMessage("<red>Index out of bounds. Valid range: 0 - " + Config.PRIZES.LIST.size() + "</red>");
return true;
}
Config.PRIZES.setPrizeAt(index, item.clone());
player.sendRichMessage("<green>Set prize at index </green><yellow>" + index + "</yellow>");
} catch (NumberFormatException e) {
player.sendRichMessage("<red>Invalid index. Usage: /easter setprize [index]</red>");
}
} else {
Config.PRIZES.addPrize(item.clone());
player.sendRichMessage(Messages.SETPRIZE.SUCCESS);
}
return true;
}
@Override
public String getName() {
return "setprize";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
if (args.length == 2) {
List<String> res = new ArrayList<>();
int maxIndex = Config.PRIZES.LIST.size();
for (int i = 0; i <= maxIndex; i++) {
res.add(String.valueOf(i));
}
return res;
}
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.SET_PRIZE;
}
}
@@ -0,0 +1,115 @@
package com.alttd.easter.commands.subcommands;
import com.alttd.easter.commands.SubCommand;
import com.alttd.easter.config.Messages;
import com.alttd.easter.egg.EggType;
import com.alttd.easter.util.ItemUtils;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
public class SpawnEggs extends SubCommand {
@Override
public boolean onCommand(CommandSender commandSender, String[] args) {
if (!(commandSender instanceof Player player)) {
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
return true;
}
int amount = 1;
if (args.length >= 2) {
try {
amount = Math.max(1, Integer.parseInt(args[1]));
} catch (NumberFormatException ignored) {
// keep default
}
}
// Optional: allow spawning a single type via argument
if (args.length >= 2) {
EggType specific = parseEggType(args[1]);
if (specific != null) {
giveEgg(player, specific, amount);
player.sendRichMessage("<green>Spawned </green><gold>" + amount + "</gold> <green>of </green><type> <green>egg(s).</green>",
Placeholder.component("type", ItemUtils.typeNameComponent(specific))
);
return true;
}
}
if (args.length >= 3) {
EggType specific = parseEggType(args[2]);
if (specific != null) {
giveEgg(player, specific, amount);
player.sendRichMessage("<green>Spawned </green><gold>" + amount + "</gold> <green>of </green><type> <green>egg(s).</green>",
Placeholder.component("type", ItemUtils.typeNameComponent(specific))
);
return true;
}
}
for (EggType type : EggType.values()) {
giveEgg(player, type, amount);
}
player.sendRichMessage("<green>Spawned </green><gold>" + amount + "</gold> <green>of every easter egg in your inventory for debugging.</green>");
return true;
}
private void giveEgg(Player player, EggType type, int amount) {
ItemStack item = ItemUtils.createEggItem(type);
item.setAmount(Math.max(1, Math.min(64, amount)));
var left = player.getInventory().addItem(item);
if (!left.isEmpty()) {
// drop leftovers at the player's location
left.values().forEach(stack -> player.getWorld().dropItemNaturally(player.getLocation(), stack));
}
}
private EggType parseEggType(String arg) {
String key = arg.trim().toUpperCase().replace('-', '_').replace(' ', '_');
// try direct enum name
try {
return EggType.valueOf(key);
} catch (IllegalArgumentException ignored) {
}
// try match by pretty color words contained
for (EggType type : EggType.values()) {
if (ItemUtils.prettify(type.name()).replace(" ", "").equalsIgnoreCase(arg.replace(" ", ""))) {
return type;
}
}
return null;
}
@Override
public String getName() {
return "spawneggs";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
List<String> res = new ArrayList<>();
if (args.length == 2) {
// amount suggestions
res.addAll(List.of("1", "4", "16", "64"));
// also allow type in 2nd arg for convenience
for (EggType type : EggType.values()) {
res.add(type.name().toLowerCase());
}
} else if (args.length == 3) {
for (EggType type : EggType.values()) {
res.add(type.name().toLowerCase());
}
}
return res;
}
@Override
public String getHelpMessage() {
return Messages.HELP.SPAWN_EGGS;
}
}
@@ -19,7 +19,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @SuppressWarnings({"unused", "SameParameterValue"}) @Slf4j
@SuppressWarnings({"unused", "SameParameterValue"})
abstract class AbstractConfig { abstract class AbstractConfig {
File file; File file;
YamlConfiguration yaml; YamlConfiguration yaml;
@@ -129,8 +130,7 @@ abstract class AbstractConfig {
final ConfigurationSection section = yaml.getConfigurationSection(path); final ConfigurationSection section = yaml.getConfigurationSection(path);
if (section != null) { if (section != null) {
for (String key : section.getKeys(false)) { for (String key : section.getKeys(false)) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked") final T val = (T) section.get(key);
final T val = (T) section.get(key);
if (val != null) { if (val != null) {
builder.put(key, val); builder.put(key, val);
} }
+105 -10
View File
@@ -1,13 +1,16 @@
package com.alttd.easter.config; package com.alttd.easter.config;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.inventory.ItemStack;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.ArrayList;
import java.util.Set; import java.util.List;
@Slf4j public class Config extends AbstractConfig{ @Slf4j
public class Config extends AbstractConfig {
static Config config; static Config config;
@@ -26,15 +29,107 @@ import java.util.Set;
config.readConfig(Config.class, null); config.readConfig(Config.class, null);
} }
public static class SETTINGS { public static class EGGS {
private static final String prefix = "settings."; private static final String prefix = "eggs.";
public static boolean DEBUG = false; public static double EASY_CHANCE = 0.01;
public static boolean WARNINGS = true; public static double NORMAL_CHANCE = 0.05;
public static double HARD_CHANCE = 1;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static void load() { private static void load() {
DEBUG = config.getBoolean(prefix, "debug", DEBUG); EASY_CHANCE = config.getDouble(prefix, "easy-chance", EASY_CHANCE);
WARNINGS = config.getBoolean(prefix, "warnings", WARNINGS); NORMAL_CHANCE = config.getDouble(prefix, "normal-chance", NORMAL_CHANCE);
HARD_CHANCE = config.getDouble(prefix, "hard-chance", HARD_CHANCE);
// ensure paths exist in config file with their current values
config.yaml.addDefault(prefix + "easy-chance", EASY_CHANCE);
config.yaml.addDefault(prefix + "normal-chance", NORMAL_CHANCE);
config.yaml.addDefault(prefix + "hard-chance", HARD_CHANCE);
}
}
public static class RABBIT {
private static final String prefix = "rabbit.";
public static String WORLD = "world";
public static double X = 0, Y = 64, Z = 0;
public static float YAW = 0, PITCH = 0;
public static double INTERACT_RADIUS = 2.5;
@SuppressWarnings("unused")
private static void load() {
WORLD = config.getString(prefix, "world", WORLD);
X = config.getDouble(prefix, "x", X);
Y = config.getDouble(prefix, "y", Y);
Z = config.getDouble(prefix, "z", Z);
YAW = (float) config.getDouble(prefix, "yaw", YAW);
PITCH = (float) config.getDouble(prefix, "pitch", PITCH);
INTERACT_RADIUS = config.getDouble(prefix, "interact-radius", INTERACT_RADIUS);
}
public static Location getLocation(org.bukkit.Server server) {
World world = server.getWorld(WORLD);
if (world == null) {
return null;
}
return new Location(world, X, Y, Z, YAW, PITCH);
}
}
public static class PRIZES {
private static final String prefix = "prizes.";
public static List<ItemStack> LIST = new ArrayList<>();
private static int NEXT_INDEX = 0;
@SuppressWarnings("unused")
private static void load() {
List<?> raw = config.yaml.getList(prefix + "list");
LIST = new ArrayList<>();
if (raw != null) {
for (Object o : raw) {
if (o instanceof ItemStack item) {
LIST.add(item);
}
}
}
// ensure path exists
config.yaml.addDefault(prefix + "list", LIST);
// load next index and clamp to range
NEXT_INDEX = config.getInt(prefix, "next-index", 0);
if (LIST.isEmpty()) {
NEXT_INDEX = 0;
} else if (NEXT_INDEX < 0 || NEXT_INDEX >= LIST.size()) {
NEXT_INDEX = NEXT_INDEX % LIST.size();
if (NEXT_INDEX < 0) NEXT_INDEX += LIST.size();
}
config.yaml.addDefault(prefix + "next-index", NEXT_INDEX);
}
public static void addPrize(ItemStack item) {
LIST.add(item);
config.set(prefix, "list", LIST);
// keep NEXT_INDEX valid
if (NEXT_INDEX < 0 || NEXT_INDEX >= LIST.size()) {
NEXT_INDEX = 0;
}
config.set(prefix, "next-index", NEXT_INDEX);
}
public static void setPrizeAt(int index, ItemStack item) {
if (index < 0 || index > LIST.size()) {
throw new IndexOutOfBoundsException("Index must be between 0 and " + LIST.size());
}
if (index == LIST.size()) {
LIST.add(item);
} else {
LIST.set(index, item);
}
config.set(prefix, "list", LIST);
// adjust NEXT_INDEX if necessary
if (LIST.isEmpty()) {
NEXT_INDEX = 0;
} else if (NEXT_INDEX >= LIST.size()) {
NEXT_INDEX = NEXT_INDEX % LIST.size();
}
config.set(prefix, "next-index", NEXT_INDEX);
} }
} }
} }
@@ -1,7 +1,6 @@
package com.alttd.easter.config; package com.alttd.easter.config;
import java.io.File; import java.io.File;
import java.util.List;
public class Messages extends AbstractConfig { public class Messages extends AbstractConfig {
static Messages config; static Messages config;
@@ -26,12 +25,16 @@ public class Messages extends AbstractConfig {
public static String HELP_MESSAGE_WRAPPER = "<gold>Easter help:\n<commands></gold>"; public static String HELP_MESSAGE_WRAPPER = "<gold>Easter help:\n<commands></gold>";
public static String HELP_MESSAGE = "<green>Show this menu: <gold>/pu help</gold></green>"; public static String HELP_MESSAGE = "<green>Show this menu: <gold>/pu help</gold></green>";
public static String RELOAD = "<green>Reload the configs for Easter: <gold>/pu reload</gold></green>"; public static String RELOAD = "<green>Reload the configs for Easter: <gold>/pu reload</gold></green>";
public static String SET_PRIZE = "<green>Add the item in your hand as a prize: <gold>/pu setprize</gold></green>";
public static String SPAWN_EGGS = "<green>Give yourself debug eggs: <gold>/pu spawneggs [amount|type] [type]</gold></green>";
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static void load() { private static void load() {
HELP_MESSAGE_WRAPPER = config.getString(prefix, "help-wrapper", HELP_MESSAGE_WRAPPER); HELP_MESSAGE_WRAPPER = config.getString(prefix, "help-wrapper", HELP_MESSAGE_WRAPPER);
HELP_MESSAGE = config.getString(prefix, "help", HELP_MESSAGE); HELP_MESSAGE = config.getString(prefix, "help", HELP_MESSAGE);
RELOAD = config.getString(prefix, "reload", RELOAD); RELOAD = config.getString(prefix, "reload", RELOAD);
SET_PRIZE = config.getString(prefix, "set-prize", SET_PRIZE);
SPAWN_EGGS = config.getString(prefix, "spawn-eggs", SPAWN_EGGS);
} }
} }
@@ -46,7 +49,7 @@ public class Messages extends AbstractConfig {
private static void load() { private static void load() {
NO_PERMISSION = config.getString(prefix, "no-permission", NO_PERMISSION); NO_PERMISSION = config.getString(prefix, "no-permission", NO_PERMISSION);
PLAYER_ONLY = config.getString(prefix, "player-only", PLAYER_ONLY); PLAYER_ONLY = config.getString(prefix, "player-only", PLAYER_ONLY);
PLAYER_NOT_FOUND = config.getString(prefix, "player-only", PLAYER_NOT_FOUND); PLAYER_NOT_FOUND = config.getString(prefix, "player-not-found", PLAYER_NOT_FOUND);
} }
} }
@@ -60,4 +63,40 @@ public class Messages extends AbstractConfig {
RELOADED = config.getString(prefix, "reloaded", RELOADED); RELOADED = config.getString(prefix, "reloaded", RELOADED);
} }
} }
public static class RABBIT {
private static final String prefix = "rabbit.";
public static String LEFT_CLICK_INTRO = "<green>Hi <player>. A bunch of different mobs have stolen my easter eggs! Can you find them and kill them for me? If you bring the eggs back I will reward you!</green>";
public static String NO_EGGS = "<yellow>Please come back when you've found some eggs!</yellow>";
public static String ONE_EGG = "<green>Thank you for finding my eggs <player>! I have received the <egg1> egg from you. Please keep looking for more!</green>";
public static String MULTI_EGGS = "<green>Thank you for finding my eggs <player>! I have received <eggs> eggs from you. Please keep looking for more!</green>";
public static String REWARD = "<green>Thank you! You've turned in 4 unique eggs and earned a prize!</green>";
public static String GUI_TITLE = "<gold>Turn in Eggs</gold>";
public static String TURNED_IN_NONE = "<yellow>You haven't turned in any eggs yet.</yellow>";
public static String TURNED_IN_LIST = "<green>You have turned in: <eggs></green>";
@SuppressWarnings("unused")
private static void load() {
LEFT_CLICK_INTRO = config.getString(prefix, "left-click-intro", LEFT_CLICK_INTRO);
NO_EGGS = config.getString(prefix, "no-eggs", NO_EGGS);
REWARD = config.getString(prefix, "reward", REWARD);
GUI_TITLE = config.getString(prefix, "gui-title", GUI_TITLE);
TURNED_IN_NONE = config.getString(prefix, "turned-in-none", TURNED_IN_NONE);
TURNED_IN_LIST = config.getString(prefix, "turned-in-list", TURNED_IN_LIST);
ONE_EGG = config.getString(prefix, "one-egg", ONE_EGG);
MULTI_EGGS = config.getString(prefix, "multi-eggs", MULTI_EGGS);
}
}
public static class SETPRIZE {
private static final String prefix = "setprize.";
public static String SUCCESS = "<green>Added the item in your hand as a possible prize.</green>";
public static String EMPTY_HAND = "<red>You must hold an item to set it as a prize.</red>";
@SuppressWarnings("unused")
private static void load() {
SUCCESS = config.getString(prefix, "success", SUCCESS);
EMPTY_HAND = config.getString(prefix, "empty-hand", EMPTY_HAND);
}
}
} }
@@ -0,0 +1,68 @@
package com.alttd.easter.data;
import com.alttd.easter.egg.EggType;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class DataManager {
private final File file;
private final YamlConfiguration yaml;
private final Map<UUID, Set<EggType>> playerEggs = new HashMap<>();
public DataManager(org.bukkit.plugin.Plugin plugin) {
this.file = new File(plugin.getDataFolder(), "data.yml");
this.yaml = new YamlConfiguration();
if (file.exists()) {
try {
yaml.load(file);
} catch (Exception ignored) {
}
}
load();
}
private void load() {
for (String key : yaml.getKeys(false)) {
try {
UUID uuid = UUID.fromString(key);
List<String> list = yaml.getStringList(key);
Set<EggType> types = list.stream().map(s -> {
try {
return EggType.valueOf(s);
} catch (Exception e) {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toSet());
playerEggs.put(uuid, types);
} catch (IllegalArgumentException ignored) {
}
}
}
public void save() {
for (Map.Entry<UUID, Set<EggType>> e : playerEggs.entrySet()) {
List<String> list = e.getValue().stream().map(Enum::name).toList();
yaml.set(e.getKey().toString(), list);
}
try {
yaml.save(file);
} catch (IOException ignored) {
}
}
public Set<EggType> getPlayerEggs(UUID uuid) {
return playerEggs.computeIfAbsent(uuid, u -> new HashSet<>());
}
public int addEggs(UUID uuid, Collection<EggType> newTypes) {
Set<EggType> set = getPlayerEggs(uuid);
int before = set.size();
set.addAll(newTypes);
return set.size() - before; // number of newly added uniques
}
}
@@ -0,0 +1,58 @@
package com.alttd.easter.egg;
import lombok.Getter;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
public enum EggType {
BROWN_HUSK(Material.BROWN_GLAZED_TERRACOTTA, EntityType.HUSK, Enchantment.FIRE_PROTECTION, 2, Difficulty.NORMAL, TextColor.color(0x8F4F2A)),
RED_SPIDER(Material.RED_GLAZED_TERRACOTTA, EntityType.SPIDER, Enchantment.BANE_OF_ARTHROPODS, 2, Difficulty.NORMAL, TextColor.color(0xB02E26)),
ORANGE_BLAZE(Material.ORANGE_GLAZED_TERRACOTTA, EntityType.BLAZE, Enchantment.FIRE_ASPECT, 2, Difficulty.NORMAL, TextColor.color(0xF9801D)),
YELLOW_PIGLIN(Material.YELLOW_GLAZED_TERRACOTTA, EntityType.PIGLIN, Enchantment.FORTUNE, 2, Difficulty.NORMAL, TextColor.color(0xFED83D)),
LIME_CREEPER(Material.LIME_GLAZED_TERRACOTTA, EntityType.CREEPER, Enchantment.BLAST_PROTECTION, 2, Difficulty.NORMAL, TextColor.color(0x80C71F)),
GREEN_ZOMBIE(Material.GREEN_GLAZED_TERRACOTTA, EntityType.ZOMBIE, Enchantment.FEATHER_FALLING, 2, Difficulty.NORMAL, TextColor.color(0x5E7C16)),
LIGHT_BLUE_STRAY(Material.LIGHT_BLUE_GLAZED_TERRACOTTA, EntityType.STRAY, Enchantment.FROST_WALKER, 2, Difficulty.NORMAL, TextColor.color(0x3AB3DA)),
CYAN_GUARDIAN(Material.CYAN_GLAZED_TERRACOTTA, EntityType.GUARDIAN, Enchantment.THORNS, 2, Difficulty.EASY, TextColor.color(0x169C9C)),
BLUE_WARDEN(Material.BLUE_GLAZED_TERRACOTTA, EntityType.WARDEN, Enchantment.SWIFT_SNEAK, 2, Difficulty.HARD, TextColor.color(0x3C44AA)),
PURPLE_ENDER_DRAGON(Material.PURPLE_GLAZED_TERRACOTTA, EntityType.ENDER_DRAGON, Enchantment.POWER, 2, Difficulty.HARD, TextColor.color(0x8932B8)),
MAGENTA_ENDERMAN(Material.MAGENTA_GLAZED_TERRACOTTA, EntityType.ENDERMAN, Enchantment.INFINITY, 2, Difficulty.EASY, TextColor.color(0xC74EBD)),
PINK_HOGLIN(Material.PINK_GLAZED_TERRACOTTA, EntityType.HOGLIN, Enchantment.PIERCING, 2, Difficulty.NORMAL, TextColor.color(0xF38BAA)),
WHITE_SKELETON(Material.WHITE_GLAZED_TERRACOTTA, EntityType.SKELETON, Enchantment.MULTISHOT, 2, Difficulty.NORMAL, TextColor.color(0xFFFFFF)),
LIGHT_GRAY_SILVERFISH(Material.LIGHT_GRAY_GLAZED_TERRACOTTA, EntityType.SILVERFISH, Enchantment.EFFICIENCY, 2, Difficulty.EASY, TextColor.color(0x9D9D97)),
GRAY_WITHER(Material.GRAY_GLAZED_TERRACOTTA, EntityType.WITHER, Enchantment.SMITE, 2, Difficulty.HARD, TextColor.color(0x474F52)),
BLACK_WITHER_SKELETON(Material.BLACK_GLAZED_TERRACOTTA, EntityType.WITHER_SKELETON, Enchantment.LOOTING, 2, Difficulty.NORMAL, TextColor.color(0x1D1D21));
public enum Difficulty {EASY, NORMAL, HARD}
@Getter
private final Material material;
private final EntityType entityType;
@Getter
private final Enchantment enchantment;
@Getter
private final int enchantLevel;
@Getter
private final Difficulty difficulty;
@Getter
private final TextColor color;
EggType(Material material, EntityType entityType, Enchantment enchantment, int enchantLevel, Difficulty difficulty, TextColor color) {
this.material = material;
this.entityType = entityType;
this.enchantment = enchantment;
this.enchantLevel = enchantLevel;
this.difficulty = difficulty;
this.color = color;
}
public static EggType fromEntityType(EntityType entityType) {
for (EggType type : values()) {
if (type.entityType == entityType) {
return type;
}
}
return null;
}
}
@@ -0,0 +1,161 @@
package com.alttd.easter.gui;
import com.alttd.easter.config.Config;
import com.alttd.easter.config.Messages;
import com.alttd.easter.data.DataManager;
import com.alttd.easter.egg.EggType;
import com.alttd.easter.util.ItemUtils;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
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.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.*;
@Slf4j
public class TurnInGuiManager implements Listener {
private final DataManager dataManager;
private final Map<UUID, Inventory> openInventories = new HashMap<>();
public TurnInGuiManager(DataManager dataManager) {
this.dataManager = dataManager;
}
private Component title() {
return MiniMessage.miniMessage().deserialize(Messages.RABBIT.GUI_TITLE);
}
public void open(Player player) {
Inventory inv = Bukkit.createInventory(player, 27, title());
openInventories.put(player.getUniqueId(), inv);
player.openInventory(inv);
}
@EventHandler
public void onClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
Inventory top = openInventories.get(player.getUniqueId());
if (top == null) {
return;
}
if (!event.getView().getTopInventory().equals(top)) {
return;
}
// If clicking in player inventory with shift-click, prevent moving non-egg items into GUI
if (event.isShiftClick() && event.getClickedInventory() != null && event.getClickedInventory().equals(player.getInventory())) {
ItemStack stack = event.getCurrentItem();
if (stack != null && !stack.getType().isAir() && !ItemUtils.isEggItem(stack)) {
event.setCancelled(true);
return;
}
}
// If placing an item into the GUI (cursor over top inventory), only allow egg items
boolean inTop = event.getRawSlot() < top.getSize() && event.getRawSlot() >= 0;
if (inTop) {
ItemStack cursor = event.getCursor();
if (!cursor.getType().isAir() && !ItemUtils.isEggItem(cursor)) {
event.setCancelled(true);
}
}
}
@EventHandler
public void onClose(InventoryCloseEvent event) {
Player player = (Player) event.getPlayer();
Inventory top = openInventories.get(player.getUniqueId());
if (top == null) {
return;
}
if (!event.getView().getTopInventory().equals(top)) {
return;
}
openInventories.remove(player.getUniqueId());
// collect egg items for submission and then clear GUI so eggs don't drop
Set<EggType> submitted = new HashSet<>();
for (ItemStack item : top.getContents()) {
if (ItemUtils.isEggItem(item)) {
EggType type = ItemUtils.getEggType(item);
if (type != null) {
submitted.add(type);
}
}
}
top.clear();
if (submitted.isEmpty()) {
player.sendRichMessage(Messages.RABBIT.NO_EGGS);
return;
}
// determine how many new unique egg types were added so we can award correct number of prizes
int prevTotal = dataManager.getPlayerEggs(player.getUniqueId()).size();
int newlyAdded = dataManager.addEggs(player.getUniqueId(), submitted);
dataManager.save();
// Dialog formatting for submission summary
List<Component> eggComponents = new ArrayList<>();
for (EggType type : submitted) {
eggComponents.add(ItemUtils.colorNameComponent(type));
}
if (eggComponents.size() == 1) {
player.sendRichMessage(Messages.RABBIT.ONE_EGG,
Placeholder.parsed("player", player.getName()),
Placeholder.component("egg1", eggComponents.getFirst())
);
} else {
Component eggsJoined;
if (eggComponents.size() == 2) {
eggsJoined = Component.join(
JoinConfiguration.builder()
.lastSeparator(Component.text(" and "))
.build(),
eggComponents
);
} else {
eggsJoined = Component.join(
JoinConfiguration.builder()
.separator(Component.text(", "))
.lastSeparator(Component.text(", and "))
.build(),
eggComponents
);
}
player.sendRichMessage(Messages.RABBIT.MULTI_EGGS,
Placeholder.parsed("player", player.getName()),
Placeholder.component("eggs", eggsJoined)
);
}
int newTotal = prevTotal + newlyAdded;
int prevMilestones = prevTotal / 4;
int newMilestones = newTotal / 4;
int prizesToGive = Math.max(0, newMilestones - prevMilestones);
List<ItemStack> prizeList = Config.PRIZES.LIST;
for (int i = 0; i < prizesToGive; i++) {
int intendedIndex = prevMilestones + i;
if (prizeList.isEmpty()) {
log.warn("No prizes configured. Tried to give prize for intended index {}. Staff should configure prizes using /easter setprize.", intendedIndex);
} else {
int wrappedIndex = intendedIndex % prizeList.size();
if (intendedIndex >= prizeList.size() || intendedIndex < 0) {
log.warn("Prize index overflow: intended index {} -> wrapped to {} (prize list size {}).", intendedIndex, wrappedIndex, prizeList.size());
}
ItemStack prize = prizeList.get(wrappedIndex).clone();
player.getInventory().addItem(prize);
}
player.sendRichMessage(Messages.RABBIT.REWARD);
}
}
}
@@ -0,0 +1,41 @@
package com.alttd.easter.listeners;
import com.alttd.easter.config.Config;
import com.alttd.easter.egg.EggType;
import com.alttd.easter.util.ItemUtils;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Mob;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import java.util.concurrent.ThreadLocalRandom;
public class DeathListener implements Listener {
@EventHandler
public void onDeath(EntityDeathEvent event) {
LivingEntity entity = event.getEntity();
// Only consider AI-capable mobs
if (!(entity instanceof Mob mob) || !mob.isAware()) {
return;
}
EggType eggType = EggType.fromEntityType(entity.getType());
if (eggType == null) {
return;
}
double chance;
switch (eggType.getDifficulty()) {
case EASY -> chance = Config.EGGS.EASY_CHANCE;
case NORMAL -> chance = Config.EGGS.NORMAL_CHANCE;
case HARD -> chance = Config.EGGS.HARD_CHANCE;
default -> chance = 0.0;
}
if (chance <= 0) {
return;
}
if (ThreadLocalRandom.current().nextDouble() < chance) {
event.getDrops().add(ItemUtils.createEggItem(eggType));
}
}
}
@@ -0,0 +1,117 @@
package com.alttd.easter.npc;
import com.alttd.easter.config.Config;
import com.alttd.easter.config.Messages;
import com.alttd.easter.data.DataManager;
import com.alttd.easter.egg.EggType;
import com.alttd.easter.gui.TurnInGuiManager;
import com.alttd.easter.util.ItemUtils;
import com.alttd.easter.util.Keys;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@SuppressWarnings("ClassCanBeRecord")
public class RabbitNpcManager implements Listener {
private final org.bukkit.plugin.Plugin plugin;
private final com.alttd.easter.gui.TurnInGuiManager turnInGuiManager;
private final DataManager dataManager;
public RabbitNpcManager(Plugin plugin, TurnInGuiManager turnInGuiManager, DataManager dataManager) {
this.plugin = plugin;
this.turnInGuiManager = turnInGuiManager;
this.dataManager = dataManager;
spawnOrFindRabbit();
}
public void reload() {
spawnOrFindRabbit();
}
private void spawnOrFindRabbit() {
// Find existing rabbit with tag, else spawn new at config location
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntitiesByClass(Rabbit.class)) {
if (entity.getPersistentDataContainer().has(Keys.RABBIT_NPC, PersistentDataType.BYTE)) {
return;
}
}
}
var loc = Config.RABBIT.getLocation(plugin.getServer());
if (loc == null) {
return;
}
Rabbit rabbit = (Rabbit) loc.getWorld().spawnEntity(loc, EntityType.RABBIT);
rabbit.setCustomNameVisible(true);
rabbit.setInvulnerable(true);
rabbit.setSilent(true);
rabbit.setGlowing(true);
rabbit.setGravity(false);
rabbit.setAI(false);
rabbit.customName(Component.text("Easter Bunny"));
rabbit.getPersistentDataContainer().set(Keys.RABBIT_NPC, PersistentDataType.BYTE, (byte) 1);
}
@EventHandler
public void onInteract(PlayerInteractAtEntityEvent event) {
if (!(event.getRightClicked() instanceof Rabbit)
&& !(event.getRightClicked() instanceof LivingEntity livingEntity
&& livingEntity.getPersistentDataContainer().has(Keys.RABBIT_NPC, PersistentDataType.BYTE))) {
return;
}
event.setCancelled(true);
Player player = event.getPlayer();
// Right click -> open GUI
openTurnIn(player);
}
@EventHandler
public void onLeftClick(EntityDamageByEntityEvent event) {
if (!(event.getEntity() instanceof org.bukkit.entity.LivingEntity le)) {
return;
}
if (!le.getPersistentDataContainer().has(Keys.RABBIT_NPC, PersistentDataType.BYTE)) {
return;
}
if (!(event.getDamager() instanceof Player player)) {
return;
}
event.setCancelled(true);
player.sendRichMessage(Messages.RABBIT.LEFT_CLICK_INTRO,
Placeholder.parsed("player", player.getName())
);
var eggs = dataManager.getPlayerEggs(player.getUniqueId());
if (eggs.isEmpty()) {
player.sendRichMessage(Messages.RABBIT.TURNED_IN_NONE);
return;
}
List<EggType> sorted = eggs.stream()
.sorted(Comparator.comparingInt(Enum::ordinal))
.toList();
var components = sorted.stream()
.map(ItemUtils::colorNameComponent)
.toList();
var joined = net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.builder()
.separator(net.kyori.adventure.text.Component.text(", "))
.build(), components);
player.sendRichMessage(Messages.RABBIT.TURNED_IN_LIST,
Placeholder.component("eggs", joined));
}
private void openTurnIn(Player player) {
turnInGuiManager.open(player);
}
}
@@ -0,0 +1,99 @@
package com.alttd.easter.util;
import com.alttd.easter.egg.EggType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
import java.util.ArrayList;
import java.util.List;
public final class ItemUtils {
private static String colorName(EggType type) {
return switch (type) {
case BROWN_HUSK -> "Brown";
case RED_SPIDER -> "Red";
case ORANGE_BLAZE -> "Orange";
case YELLOW_PIGLIN -> "Yellow";
case LIME_CREEPER -> "Lime";
case GREEN_ZOMBIE -> "Green";
case LIGHT_BLUE_STRAY -> "Light Blue";
case CYAN_GUARDIAN -> "Cyan";
case BLUE_WARDEN -> "Blue";
case PURPLE_ENDER_DRAGON -> "Purple";
case MAGENTA_ENDERMAN -> "Magenta";
case PINK_HOGLIN -> "Pink";
case WHITE_SKELETON -> "White";
case LIGHT_GRAY_SILVERFISH -> "Light Gray";
case GRAY_WITHER -> "Gray";
case BLACK_WITHER_SKELETON -> "Black";
};
}
public static Component colorNameComponent(EggType type) {
return Component.text(colorName(type), type.getColor());
}
public static Component typeNameComponent(EggType type) {
return Component.text(prettify(type.name()), type.getColor());
}
public static ItemStack createEggItem(EggType type) {
ItemStack item = new ItemStack(type.getMaterial());
ItemMeta meta = item.getItemMeta();
meta.addEnchant(type.getEnchantment(), type.getEnchantLevel(), true);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
Component name = MiniMessage.miniMessage().deserialize("<gold>Easter Egg - </gold><type>",
Placeholder.component("type", typeNameComponent(type))
);
meta.displayName(name);
meta.getPersistentDataContainer().set(Keys.EGG_ITEM, PersistentDataType.STRING, type.name());
item.setItemMeta(meta);
return item;
}
public static boolean isEggItem(ItemStack item) {
if (item == null) {
return false;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return false;
}
return meta.getPersistentDataContainer().has(Keys.EGG_ITEM, PersistentDataType.STRING);
}
public static EggType getEggType(ItemStack item) {
if (!isEggItem(item)) {
return null;
}
String name = item.getItemMeta().getPersistentDataContainer().get(Keys.EGG_ITEM, PersistentDataType.STRING);
try {
return name == null ? null : EggType.valueOf(name);
} catch (IllegalArgumentException ex) {
return null;
}
}
public static String prettify(String name) {
String[] parts = name.toLowerCase().split("_");
List<String> out = new ArrayList<>();
for (String p : parts) {
if (p.isEmpty()) {
continue;
}
out.add(Character.toUpperCase(p.charAt(0)) + p.substring(1));
}
return String.join(" ", out);
}
private ItemUtils() {
}
}
@@ -0,0 +1,13 @@
package com.alttd.easter.util;
import com.alttd.easter.Easter;
import org.bukkit.NamespacedKey;
public final class Keys {
public static final NamespacedKey EGG_ITEM = new NamespacedKey(Easter.getPlugin(Easter.class), "egg-item");
public static final NamespacedKey EGG_ENTITY = new NamespacedKey(Easter.getPlugin(Easter.class), "egg-entity");
public static final NamespacedKey RABBIT_NPC = new NamespacedKey(Easter.getPlugin(Easter.class), "rabbit-npc");
private Keys() {
}
}
+25
View File
@@ -0,0 +1,25 @@
name: Easter
main: com.alttd.easter.Easter
version: ${version}
description: Altitude's Easter plugin
api-version: '1.21'
author: Altitude
commands:
easter:
description: Easter main command
usage: /<command>
aliases: [ easter ]
permission: playerutils.use
permissions:
easter.reload:
description: Reload the Easter plugin
default: op
easter.setprize:
description: Set the held item as a prize
default: op
easter.getprizes:
description: List configured prizes with their index
default: op
easter.spawneggs:
description: Give yourself debug eggs
default: op