diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 0000000..e699880 --- /dev/null +++ b/plugin.yml @@ -0,0 +1,23 @@ +main: eu.endercentral.crazy_advancements.CrazyAdvancementsAPI +name: CrazyAdvancementsAPI +version: 2.0 +api-version: 1.13 +load: startup +author: ZockerAxel +commands: + grant: + usage: /cagrant [Criteria...] + description: Grants -[Criteria...] to in + aliases: [cagrant] + revoke: + usage: /carevoke [Criteria...] + description: Revokes -[Criteria...] from in + aliases: [carevoke] + setprogress: + usage: /setprogress + description: Sets Progress for in + aliases: [caprogress] + showtoast: + usage: /showtoast + description: Displays a Toast Advancement Message + aliases: [catoast, toast] \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..650da80 --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + eu.endercentral.crazy_advancements + CrazyAdvancementsAPI + 2.0 + jar + + CrazyAdvancementsAPI + https://crazyadvancements.endercentral.eu + + + UTF-8 + + + + + org.spigotmc + spigot-api + 1.17-R0.1-SNAPSHOT + provided + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + diff --git a/src/eu/endercentral/crazy_advancements/AdvancementPacketReceiver.java b/src/eu/endercentral/crazy_advancements/AdvancementPacketReceiver.java new file mode 100644 index 0000000..a7a907b --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/AdvancementPacketReceiver.java @@ -0,0 +1,123 @@ +package eu.endercentral.crazy_advancements; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import eu.endercentral.crazy_advancements.event.AdvancementScreenCloseEvent; +import eu.endercentral.crazy_advancements.event.AdvancementTabChangeEvent; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.MessageToMessageDecoder; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.PacketPlayInAdvancements; +import net.minecraft.network.protocol.game.PacketPlayInAdvancements.Status; + +public class AdvancementPacketReceiver { + + private static HashMap handlers = new HashMap<>(); + private static Field channelField; + + { + for(Field f : NetworkManager.class.getDeclaredFields()) { + if(f.getType().isAssignableFrom(Channel.class)) { + channelField = f; + channelField.setAccessible(true); + break; + } + } + } + + interface PacketReceivingHandler { + public boolean handle(Player p, PacketPlayInAdvancements packet); + } + + public ChannelHandler listen(final Player p, final PacketReceivingHandler handler) { + Channel ch = getNettyChannel(p); + ChannelPipeline pipe = ch.pipeline(); + + ChannelHandler handle = new MessageToMessageDecoder>() { + @Override + protected void decode(ChannelHandlerContext chc, Packet packet, List out) throws Exception { + + if(packet instanceof PacketPlayInAdvancements) { + if(!handler.handle(p, (PacketPlayInAdvancements) packet)) { + out.add(packet); + } + return; + } + + out.add(packet); + } + }; + pipe.addAfter("decoder", "endercentral_crazy_advancements_listener_" + handler.hashCode(), handle); + + + return handle; + } + + public Channel getNettyChannel(Player p) { + NetworkManager manager = ((CraftPlayer)p).getHandle().b.a; + Channel channel = null; + try { + channel = (Channel) channelField.get(manager); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + return channel; + } + + public boolean close(Player p, ChannelHandler handler) { + try { + ChannelPipeline pipe = getNettyChannel(p).pipeline(); + pipe.remove(handler); + return true; + } catch(Exception e) { + return false; + } + } + + public HashMap getHandlers() { + return handlers; + } + + public void initPlayer(Player p) { + handlers.put(p.getName(), listen(p, new PacketReceivingHandler() { + + @Override + public boolean handle(Player p, PacketPlayInAdvancements packet) { + + if(packet.c() == Status.a) { + NameKey name = new NameKey(packet.d()); + AdvancementTabChangeEvent event = new AdvancementTabChangeEvent(p, name); + Bukkit.getPluginManager().callEvent(event); + + if(event.isCancelled()) { + CrazyAdvancementsAPI.clearActiveTab(p); + return false; + } else { + if(!event.getTabAdvancement().equals(name)) { + CrazyAdvancementsAPI.setActiveTab(p, event.getTabAdvancement()); + } else { + CrazyAdvancementsAPI.setActiveTab(p, name, false); + } + } + } else { + AdvancementScreenCloseEvent event = new AdvancementScreenCloseEvent(p); + Bukkit.getPluginManager().callEvent(event); + } + + + return true; + } + })); + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/CrazyAdvancementsAPI.java b/src/eu/endercentral/crazy_advancements/CrazyAdvancementsAPI.java new file mode 100644 index 0000000..62cd142 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/CrazyAdvancementsAPI.java @@ -0,0 +1,452 @@ +package eu.endercentral.crazy_advancements; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import javax.annotation.Nullable; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.v1_17_R1.command.ProxiedNativeCommandSender; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.java.JavaPlugin; + +import com.google.gson.JsonObject; + +import eu.endercentral.crazy_advancements.advancement.Advancement; +import eu.endercentral.crazy_advancements.advancement.AdvancementDisplay.AdvancementFrame; +import eu.endercentral.crazy_advancements.advancement.ToastNotification; +import eu.endercentral.crazy_advancements.advancement.criteria.CriteriaType; +import eu.endercentral.crazy_advancements.advancement.progress.GenericResult; +import eu.endercentral.crazy_advancements.advancement.progress.GrantCriteriaResult; +import eu.endercentral.crazy_advancements.manager.AdvancementManager; +import eu.endercentral.crazy_advancements.packet.AdvancementsPacket; +import net.minecraft.advancements.Criterion; +import net.minecraft.advancements.CriterionInstance; +import net.minecraft.advancements.critereon.LootSerializationContext; +import net.minecraft.network.protocol.game.PacketPlayOutSelectAdvancementTab; +import net.minecraft.resources.MinecraftKey; + +public class CrazyAdvancementsAPI extends JavaPlugin implements Listener { + + private static CrazyAdvancementsAPI instance; + + public static final Criterion CRITERION = new Criterion(new CriterionInstance() { + @Override + public JsonObject a(LootSerializationContext arg0) { + return null; + } + + @Override + public MinecraftKey a() { + return new MinecraftKey("", ""); + } + }); + + private static AdvancementPacketReceiver packetReciever; + private static HashMap activeTabs = new HashMap<>(); + + @Override + public void onLoad() { + instance = this; + } + + @Override + public void onEnable() { + //Init Packet Receiver + packetReciever = new AdvancementPacketReceiver(); + + for(Player player : Bukkit.getOnlinePlayers()) { + packetReciever.initPlayer(player); + } + + //Register Events + Bukkit.getPluginManager().registerEvents(this, this); + } + + @Override + public void onDisable() { + for(Player player : Bukkit.getOnlinePlayers()) { + AdvancementsPacket packet = new AdvancementsPacket(player, true, null, null); + packet.send(); + } + } + + public static CrazyAdvancementsAPI getInstance() { + return instance; + } + + @EventHandler + public void onJoin(PlayerJoinEvent e) { + Player player = e.getPlayer(); + packetReciever.initPlayer(player); + } + + @EventHandler + public void quit(PlayerQuitEvent e) { + packetReciever.close(e.getPlayer(), packetReciever.getHandlers().get(e.getPlayer().getName())); + } + + /** + * Clears the active tab + * + * @param player The player whose Tab should be cleared + */ + public static void clearActiveTab(Player player) { + setActiveTab(player, null, true); + } + + /** + * Sets the active tab + * + * @param player The player whose Tab should be changed + * @param rootAdvancement The name of the tab to change to + */ + public static void setActiveTab(Player player, String rootAdvancement) { + setActiveTab(player, new NameKey(rootAdvancement)); + } + + /** + * Sets the active tab + * + * @param player The player whose Tab should be changed + * @param rootAdvancement The name of the tab to change to + */ + public static void setActiveTab(Player player, @Nullable NameKey rootAdvancement) { + setActiveTab(player, rootAdvancement, true); + } + + static void setActiveTab(Player player, NameKey rootAdvancement, boolean update) { + if(update) { + PacketPlayOutSelectAdvancementTab packet = new PacketPlayOutSelectAdvancementTab(rootAdvancement == null ? null : rootAdvancement.getMinecraftKey()); + ((CraftPlayer)player).getHandle().b.sendPacket(packet); + } + activeTabs.put(player.getUniqueId().toString(), rootAdvancement); + } + + /** + * Gets the active tab + * + * @param player Player to check + * @return The active Tab + */ + public static NameKey getActiveTab(Player player) { + return activeTabs.get(player.getUniqueId().toString()); + } + + private final String noPermission = "�cI'm sorry but you do not have permission to perform this command. Please contact the server administrator if you believe that this is in error."; + private final String commandIncompatible = "�cThis Command is incompatible with your Arguments!"; + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if(cmd.getName().equalsIgnoreCase("showtoast")) { + if(sender.hasPermission("crazyadvancements.command.*") || sender.hasPermission("crazyadvancements.command.showtoast")) { + if(args.length >= 3) { + try { + Player player = args[0].equalsIgnoreCase("@s") ? (sender instanceof Player) ? (Player) sender : (sender instanceof ProxiedNativeCommandSender) ? (Player) ((ProxiedNativeCommandSender)sender).getCallee() : null : Bukkit.getPlayer(args[0]); + + if(player != null && player.isOnline()) { + Material mat = getMaterial(args[1]); + + if(mat != null && mat.isItem()) { + String message = args[2]; + if(args.length > 3) { + for(int i = 3; i < args.length; i++) { + message += " " + args[i]; + } + } + + ToastNotification toast = new ToastNotification(mat, message, AdvancementFrame.TASK); + toast.send(player); + + sender.sendMessage("�aSuccessfully displayed Toast to �b" + player.getName() + "�a!"); + } else { + sender.sendMessage("�c'" + args[1] + "' isn't a valid Item Material"); + } + + } else { + sender.sendMessage("�cCan't find Player '�e" + args[0] + "�c'"); + } + } catch(Exception ex) { + sender.sendMessage(commandIncompatible); + } + + + } else { + sender.sendMessage("�cUsage: �r" + cmd.getUsage()); + } + } else { + sender.sendMessage(noPermission); + } + return true; + } + + if(cmd.getName().equalsIgnoreCase("grant") || cmd.getName().equalsIgnoreCase("revoke")) { + boolean grant = cmd.getName().equalsIgnoreCase("grant"); + if(sender.hasPermission("crazyadvancements.command.*") || sender.hasPermission("crazyadvancements.command.grantrevoke")) { + if(args.length >= 3) { + try { + Player player = args[0].equalsIgnoreCase("@s") ? (sender instanceof Player) ? (Player) sender : (sender instanceof ProxiedNativeCommandSender) ? (Player) ((ProxiedNativeCommandSender)sender).getCallee() : null : Bukkit.getPlayer(args[0]); + + if(player != null && player.isOnline()) { + AdvancementManager manager = AdvancementManager.getAccessibleManager(new NameKey(args[1])); + + if(manager != null) { + if(manager.getPlayers().contains(player)) { + Advancement advancement = manager.getAdvancement(new NameKey(args[2])); + + if(advancement != null) { + if(args.length >= 4) { + + String[] convertedCriteria = Arrays.copyOfRange(args, 3, args.length); + + boolean success = false; + + if(grant) { + if(!advancement.isGranted(player)) { + GrantCriteriaResult result = manager.grantCriteria(player, advancement, convertedCriteria); + success = result == GrantCriteriaResult.CHANGED; + } + } else { + GenericResult result = manager.revokeCriteria(player, advancement, convertedCriteria); + success = result == GenericResult.CHANGED; + } + + String criteriaString = "�c" + convertedCriteria[0]; + if(convertedCriteria.length > 1) { + for(String criteria : Arrays.copyOfRange(convertedCriteria, 1, convertedCriteria.length - 1)) { + criteriaString += "�a, �c" + criteria; + } + criteriaString += " �aand �c" + convertedCriteria[convertedCriteria.length - 1]; + } + + if(success) { + sender.sendMessage("�aSuccessfully " + (grant ? "granted" : "revoked") + " Criteria " + criteriaString + " �afor '�e" + advancement.getName() + "�a' " + (grant ? "to" : "from") + " �b" + player.getName()); + } else { + sender.sendMessage("�cCriteria " + criteriaString + " �afor '�e" + advancement.getName() + "�a' " + (grant ? "is already granted to" : "is already not granted to") + " �b" + player.getName()); + } + + } else { + boolean success = false; + + if(grant) { + if(!advancement.isGranted(player)) { + GenericResult result = manager.grantAdvancement(player, advancement); + success = result == GenericResult.CHANGED; + } + } else { + GenericResult result = manager.revokeAdvancement(player, advancement); + success = result == GenericResult.CHANGED; + } + + if(success) { + sender.sendMessage("�aSuccessfully " + (grant ? "granted" : "revoked") + " Advancement '�e" + advancement.getName() + "�a' " + (grant ? "to" : "from") + " �b" + player.getName()); + } else { + sender.sendMessage("�cAdvancement '�e" + advancement.getName() + "�a' " + (grant ? "is already granted to" : "is already not granted to") + " �b" + player.getName()); + } + + } + + } else { + sender.sendMessage("�cAdvancement with Name '�e" + args[2] + "�c' does not exist in '�e" + args[1] + "�c'"); + } + + } else { + sender.sendMessage("�c'�e" + args[1] + "�c' does not contain Player '�e" + args[0] + "�c'"); + } + } else { + sender.sendMessage("�cManager with Name '�e" + args[1] + "�c' does not exist"); + } + } else { + sender.sendMessage("�cCan't find Player '�e" + args[0] + "�c'"); + } + + } catch(Exception ex) { + sender.sendMessage(commandIncompatible); + } + + } else { + sender.sendMessage("�cUsage: �r" + cmd.getUsage()); + } + } else { + sender.sendMessage(noPermission); + } + return true; + } + + if(cmd.getName().equalsIgnoreCase("setprogress")) { + if(sender.hasPermission("crazyadvancements.command.*") || sender.hasPermission("crazyadvancements.command.grantrevoke")) { + if(args.length >= 3) { + try { + Player player = args[0].equalsIgnoreCase("@s") ? (sender instanceof Player) ? (Player) sender : (sender instanceof ProxiedNativeCommandSender) ? (Player) ((ProxiedNativeCommandSender)sender).getCallee() : null : Bukkit.getPlayer(args[0]); + + if(player != null && player.isOnline()) { + AdvancementManager manager = AdvancementManager.getAccessibleManager(new NameKey(args[1])); + + if(manager != null) { + if(manager.getPlayers().contains(player)) { + Advancement advancement = manager.getAdvancement(new NameKey(args[2])); + + if(advancement != null) { + if(args.length >= 4) { + + int progress = Integer.parseInt(args[3]); + manager.setCriteriaProgress(player, advancement, progress); + + sender.sendMessage("�aSuccessfully set Criteria Progress to " + progress + " �afor Advancement '�e" + advancement.getName() + "�a' for Player �b" + player.getName()); + } + + } else { + sender.sendMessage("�cAdvancement with Name '�e" + args[2] + "�c' does not exist in '�e" + args[1] + "�c'"); + } + + } else { + sender.sendMessage("�c'�e" + args[1] + "�c' does not contain Player '�e" + args[0] + "�c'"); + } + } else { + sender.sendMessage("�cManager with Name '�e" + args[1] + "�c' does not exist"); + } + } else { + sender.sendMessage("�cCan't find Player '�e" + args[0] + "�c'"); + } + + } catch(Exception ex) { + sender.sendMessage(commandIncompatible); + } + + } else { + sender.sendMessage("�cUsage: �r" + cmd.getUsage()); + } + } else { + sender.sendMessage(noPermission); + } + return true; + } + + return true; + } + + @Override + public List onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) { + ArrayList tab = new ArrayList<>(); + + if(cmd.getName().equalsIgnoreCase("showtoast")) { + if(args.length == 1) { + if("@s".startsWith(args[0])) { + tab.add("@s"); + } + for(Player player : Bukkit.getOnlinePlayers()) { + if(player.getName().toLowerCase().startsWith(args[0].toLowerCase())) { + tab.add(player.getName()); + } + } + } else if(args.length == 2) { + for(Material mat : Material.values()) { + if(mat.isItem() && mat.name().toLowerCase().startsWith(args[1].toLowerCase())) { + tab.add(mat.name().toLowerCase()); + } + } + } + + } + + if(cmd.getName().equalsIgnoreCase("grant") || cmd.getName().equalsIgnoreCase("revoke")) { + if(args.length == 1) { + if("@s".startsWith(args[0])) { + tab.add("@s"); + } + for(Player player : Bukkit.getOnlinePlayers()) { + if(player.getName().toLowerCase().startsWith(args[0].toLowerCase())) { + tab.add(player.getName()); + } + } + } else if(args.length == 2) { + for(AdvancementManager manager : AdvancementManager.getAccessibleManagers()) { + if(manager.getName().toString().startsWith(args[1].toLowerCase())) { + tab.add(manager.getName().toString()); + } + } + } else if(args.length == 3) { + AdvancementManager manager = AdvancementManager.getAccessibleManager(new NameKey(args[1])); + if(manager != null) { + for(Advancement advancement : manager.getAdvancements()) { + if(advancement.getName().toString().startsWith(args[2].toLowerCase()) || advancement.getName().getKey().startsWith(args[2].toLowerCase())) { + tab.add(advancement.getName().toString()); + } + } + } + } else if(args.length >= 4) { + AdvancementManager manager = AdvancementManager.getAccessibleManager(new NameKey(args[1])); + if(manager != null) { + Advancement advancement = manager.getAdvancement(new NameKey(args[2])); + if(advancement != null) { + for(String criterion : advancement.getCriteria().getActionNames()) { + if(criterion.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) { + tab.add(criterion); + } + } + } + } + } + + } + + if(cmd.getName().equalsIgnoreCase("setprogress")) { + if(args.length == 1) { + if("@s".startsWith(args[0])) { + tab.add("@s"); + } + for(Player player : Bukkit.getOnlinePlayers()) { + if(player.getName().toLowerCase().startsWith(args[0].toLowerCase())) { + tab.add(player.getName()); + } + } + } else if(args.length == 2) { + for(AdvancementManager manager : AdvancementManager.getAccessibleManagers()) { + if(manager.getName().toString().startsWith(args[1].toLowerCase())) { + tab.add(manager.getName().toString()); + } + } + } else if(args.length == 3) { + AdvancementManager manager = AdvancementManager.getAccessibleManager(new NameKey(args[1])); + if(manager != null) { + for(Advancement advancement : manager.getAdvancements()) { + if((advancement.getName().toString().startsWith(args[2].toLowerCase()) || advancement.getName().getKey().startsWith(args[2].toLowerCase())) && advancement.getCriteria().getType() == CriteriaType.NUMBER) { + tab.add(advancement.getName().toString()); + } + } + } + } else if(args.length == 4) { + AdvancementManager manager = AdvancementManager.getAccessibleManager(new NameKey(args[1])); + if(manager != null) { + Advancement advancement = manager.getAdvancement(new NameKey(args[2])); + if(advancement != null && advancement.getCriteria().getType() == CriteriaType.NUMBER) { + tab.add(args[3]); + tab.add("" + advancement.getCriteria().getRequiredNumber()); + } + } + } + } + + return tab; + } + + private Material getMaterial(String input) { + for(Material mat : Material.values()) { + if(mat.name().equalsIgnoreCase(input)) { + return mat; + } + } + return null; + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/JSONMessage.java b/src/eu/endercentral/crazy_advancements/JSONMessage.java new file mode 100644 index 0000000..064d924 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/JSONMessage.java @@ -0,0 +1,41 @@ +package eu.endercentral.crazy_advancements; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.chat.ComponentSerializer; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.network.chat.IChatBaseComponent.ChatSerializer; + +public class JSONMessage { + + private final BaseComponent json; + + /** + * + * @param json A JSON representation of an ingame Message {@link Read More} + */ + public JSONMessage(BaseComponent json) { + this.json = json; + } + + /** + * + * @return the JSON representation of an ingame Message + */ + public BaseComponent getJson() { + return json; + } + + /** + * + * @return An {@link IChatBaseComponent} representation of an ingame Message + */ + public IChatBaseComponent getBaseComponent() { + return ChatSerializer.a(ComponentSerializer.toString(json)); + } + + @Override + public String toString() { + return json.toPlainText(); + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/NameKey.java b/src/eu/endercentral/crazy_advancements/NameKey.java new file mode 100644 index 0000000..3765225 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/NameKey.java @@ -0,0 +1,93 @@ +package eu.endercentral.crazy_advancements; + +import net.minecraft.resources.MinecraftKey; + +public class NameKey { + + private final String namespace; + private final String key; + + private transient MinecraftKey mcKey; + + /** + * + * @param namespace The namespace, choose something representing your plugin/project + * @param key The Unique key inside your namespace + */ + public NameKey(String namespace, String key) { + this.namespace = namespace.toLowerCase(); + this.key = key.toLowerCase(); + } + + /** + * + * @param key The key inside the default namespace "minecraft" or a NameSpacedKey seperated by a colon + */ + public NameKey(String key) { + String[] split = key.split(":"); + if(split.length < 2) { + this.namespace = "minecraft"; + this.key = key.toLowerCase(); + } else { + this.namespace = split[0].toLowerCase(); + this.key = key.replaceFirst(split[0] + ":", "").toLowerCase(); + } + } + + /** + * Generates a {@link NameKey} + * + * @param from + */ + public NameKey(MinecraftKey from) { + this.namespace = from.getNamespace().toLowerCase(); + this.key = from.getKey().toLowerCase(); + } + + /** + * + * @return The namespace + */ + public String getNamespace() { + return namespace; + } + + /** + * + * @return The key + */ + public String getKey() { + return key; + } + + /** + * Compares to another key + * + * @param anotherNameKey NameKey to compare to + * @return true if both NameKeys match each other + */ + public boolean isSimilar(NameKey anotherNameKey) { + return namespace.equals(anotherNameKey.getNamespace()) && key.equals(anotherNameKey.getKey()); + } + + /** + * Gets the MinecraftKey equivalent of this NameKey + * + * @return A {@link MinecraftKey} representation of this NameKey + */ + public MinecraftKey getMinecraftKey() { + if(mcKey == null) mcKey = new MinecraftKey(namespace, key); + return mcKey; + } + + @Override + public boolean equals(Object obj) { + return isSimilar((NameKey) obj); + } + + @Override + public String toString() { + return namespace + ":" + key; + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/Advancement.java b/src/eu/endercentral/crazy_advancements/advancement/Advancement.java new file mode 100644 index 0000000..70e9e46 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/Advancement.java @@ -0,0 +1,517 @@ +package eu.endercentral.crazy_advancements.advancement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.bukkit.Bukkit; +import org.bukkit.Warning; +import org.bukkit.entity.Player; + +import eu.endercentral.crazy_advancements.NameKey; +import eu.endercentral.crazy_advancements.advancement.AdvancementDisplay.AdvancementFrame; +import eu.endercentral.crazy_advancements.advancement.criteria.Criteria; +import eu.endercentral.crazy_advancements.advancement.progress.AdvancementProgress; +import eu.endercentral.crazy_advancements.manager.AdvancementManager; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.HoverEvent.Action; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.TranslatableComponent; +import net.md_5.bungee.api.chat.hover.content.Text; + +public class Advancement { + + private final NameKey name; + private final AdvancementDisplay display; + private Criteria criteria = new Criteria(1); + private AdvancementReward reward; + + private final Advancement parent; + private HashSet children = new HashSet<>(); + private final boolean childrenTracked; + + private final List flags; + private HashMap savedVisibilityStatus; + + private HashMap progressMap = new HashMap<>(); + + /** + * Constructor for Advancements with a parent + * + * @param parent Parent advancement + * @param name Unique Identifier + * @param display The Display of the Advancement + * @param flags The flags which apply to this Advancement + */ + public Advancement(@Nullable Advancement parent, NameKey name, AdvancementDisplay display, AdvancementFlag... flags) { + this.parent = parent; + this.childrenTracked = true; + if(this.parent != null) this.parent.addChild(this); + this.name = name; + this.display = display; + this.flags = Arrays.asList(flags); + } + + /** + * Constructor for Advancements with a parent and the option to disable children tracking + * + * @param parent Parent advancement + * @param name Unique Identifier + * @param display The Display of the Advancement + * @param childrenTracking Whether children will be tracked. If false, direct children will not be cached resulting in methods like getRow() not containing + * advancements after this one. Advancements before this one will also be missing these advancements for the respective methods. Also certain behavior like + * AdvancementVisibility might not work as intended + * @param flags The flags which apply to this Advancement + */ + public Advancement(@Nullable Advancement parent, NameKey name, AdvancementDisplay display, boolean childrenTracking, AdvancementFlag... flags) { + this.parent = null; + this.childrenTracked = childrenTracking; + if(this.parent != null) this.parent.addChild(this); + this.name = name; + this.display = display; + this.flags = Arrays.asList(flags); + } + + /** + * Constructor for Root Advancements + * + * @param name Unique Identifier + * @param display The Display of the Advancement + * @param flags The flags which apply to this Advancement + */ + public Advancement(NameKey name, AdvancementDisplay display, AdvancementFlag... flags) { + this.parent = null; + this.childrenTracked = true; + if(this.parent != null) this.parent.addChild(this); + this.name = name; + this.display = display; + this.flags = Arrays.asList(flags); + } + + /** + * Constructor for Root Advancements with the option to disable children tracking + * + * @param name Unique Identifier + * @param display The Display of the Advancement + * @param childrenTracking Whether children will be tracked. If false, direct children will not be cached resulting in methods like getRow() not containing + * advancements after this one. Advancements before this one will also be missing these advancements for the respective methods. Also certain behavior like + * AdvancementVisibility might not work as intended + * @param flags The flags which apply to this Advancement + */ + public Advancement(NameKey name, AdvancementDisplay display, boolean childrenTracking, AdvancementFlag... flags) { + this.parent = null; + this.childrenTracked = childrenTracking; + if(this.parent != null) this.parent.addChild(this); + this.name = name; + this.display = display; + this.flags = Arrays.asList(flags); + } + + /** + * Get the Unique Identifier of this Advancement + * + * @return The Unique Identifier for this Advancement + */ + public NameKey getName() { + return name; + } + + /** + * Checks whether this Advancement has a specific Name + * + * @param key Key to check + * @return true if {@link Advancement} name and key share the same namespace and name + */ + public boolean hasName(NameKey key) { + return key.getNamespace().equalsIgnoreCase(name.getNamespace()) && key.getKey().equalsIgnoreCase(name.getKey()); + } + + /** + * Get the Display of this Advancement + * + * @return the Display of this Advancement + */ + public AdvancementDisplay getDisplay() { + return display; + } + + /** + * Sets the required Criteria that needs to be met in order to complete this Advancement + * + * @param criteria The required Criteria + */ + public void setCriteria(Criteria criteria) { + this.criteria = criteria; + this.progressMap.clear(); + } + + /** + * Gets the required Criteria that needs to be met in order to complete this Advancement + * + * @return The required Criteria + */ + public Criteria getCriteria() { + return criteria; + } + + /** + * Sets the Reward of this Advancement + * + * @param reward The Reward that should be given to Players upon completing this Advancement + */ + public void setReward(AdvancementReward reward) { + this.reward = reward; + } + + /** + * Gets the Reward of this Advancement + * + * @return The Reward that gets awarded to Players upon completing this Advancement + */ + public AdvancementReward getReward() { + return reward; + } + + /** + * Gets the Parent of this Advancement + * + * @return The Parent of this Advancement + */ + public Advancement getParent() { + return parent; + } + + /** + * Gets whether this Advancement is a Root Advancement + * + * @return Whether this Advancement is a Root Advancement + */ + public boolean isRoot() { + return getParent() == null; + } + + /** + * Registers a Child + * + * @param adv The Child + */ + private void addChild(Advancement adv) { + if(adv.getParent() == this && childrenTracked) children.add(adv); + } + + /** + * Gets all direct Children + * + * @return All direct Children + */ + public HashSet getChildren() { + return new HashSet<>(children); + } + + /** + * Gets the Root Advancement + * + * @return The Root Advancement + */ + public Advancement getRootAdvancement() { + if(parent == null) { + return this; + } else { + return parent.getRootAdvancement(); + } + } + + /** + * Gets the Tab + * + * @return NameKey of the Tabs Root Advancement + */ + public NameKey getTab() { + return getRootAdvancement().getName(); + } + + /** + * Gets all parents and children + * + * @return All parents and children + */ + public List getRow() { + List row = new ArrayList<>(); + row.add(this); + if(getParent() != null) { + for(Advancement untilRow : getParent().getRowUntil()) { + if(!row.contains(untilRow)) row.add(untilRow); + } + Collections.reverse(row); + } + for(Advancement child : getChildren()) { + for(Advancement afterRow : child.getRowAfter()) { + if(!row.contains(afterRow)) row.add(afterRow); + } + } + return row; + } + + /** + * Gets all parents + * + * @return All parents + */ + public List getRowUntil() { + List row = new ArrayList<>(); + row.add(this); + if(getParent() != null) { + for(Advancement untilRow : getParent().getRowUntil()) { + if(!row.contains(untilRow)) row.add(untilRow); + } + } + return row; + } + + /** + * Gets all children + * + * @return All children + */ + public List getRowAfter() { + List row = new ArrayList<>(); + row.add(this); + for(Advancement child : getChildren()) { + for(Advancement afterRow : child.getRowAfter()) { + if(!row.contains(afterRow)) row.add(afterRow); + } + } + return row; + } + + /** + * Checks whether any parents have been granted + * + * @param player Player to check + * @return true if any parent is granted + */ + public boolean isAnythingGrantedUntil(Player player) { + for(Advancement until : getRowUntil()) { + if(until.isGranted(player)) return true; + } + return false; + } + + /** + * Checks whether any children have been granted + * + * @param player Player to check + * @return true if any child is granted + */ + public boolean isAnythingGrantedAfter(Player player) { + for(Advancement after : getRowAfter()) { + if(after.isGranted(player)) return true; + } + return false; + } + + /** + * Gets a player's progress + * + * @param player The player to check + * @return The progress + */ + public AdvancementProgress getProgress(Player player) { + return getProgress(player.getUniqueId()); + } + + /** + * Gets a player's progress + * + * @param uuid The uuid of the player to check + * @return + */ + public AdvancementProgress getProgress(UUID uuid) { + if(!progressMap.containsKey(uuid.toString())) { + progressMap.put(uuid.toString(), new AdvancementProgress(getCriteria().getCriteria(), getCriteria().getRequirements())); + } + return progressMap.get(uuid.toString()); + } + + /** + * Unloads the progress
+ * Will not update the player, use {@link AdvancementManager#unloadProgress(Player player, Advancement... advancements)} for online Players + * + * @param player Player to unload progress + */ + public void unloadProgress(Player player) { + unloadProgress(player.getUniqueId()); + } + + /** + * Unloads the progress
+ * Will not update the player, use {@link AdvancementManager#unloadProgress(UUID uuid, Advancement... advancements)} for online Players + * + * @param uuid UUID of Player to unload progress + */ + public void unloadProgress(UUID uuid) { + progressMap.remove(uuid.toString()); + } + + /** + * Checks whether this Adavncement is granted to a certain player + * + * @param player Player to check + * @return true if advancement is granted + */ + public boolean isGranted(Player player) { + return getProgress(player).getNmsProgress().isDone(); + } + + /** + * Checks whether this Adavncement is granted to a certain player + * + * @param uuid The uuid of the Player to check + * @return true if advancement is granted + */ + public boolean isGranted(UUID uuid) { + return getProgress(uuid).getNmsProgress().isDone(); + } + + /** + * Gets a list of the applied Flags + * + * @return The list containing the flags + */ + public List getFlags() { + return new ArrayList<>(flags); + } + + /** + * Checks whether this advancement has a certain flag + * + * @param flag The flag to check for + * @return Whether this advancement has the specified flag + */ + public boolean hasFlag(AdvancementFlag flag) { + return flags.contains(flag); + } + + @Warning(reason = "Only use if you know what you are doing!") + public void saveVisibilityStatus(Player player, boolean visible) { + if(savedVisibilityStatus == null) savedVisibilityStatus = new HashMap<>(); + savedVisibilityStatus.put(player.getUniqueId().toString(), visible); + } + + public boolean getVisibilityStatus(Player player) { + if(savedVisibilityStatus == null) savedVisibilityStatus = new HashMap<>(); + if(!savedVisibilityStatus.containsKey(player.getUniqueId().toString())) savedVisibilityStatus.put(player.getUniqueId().toString(), getDisplay().isVisible(player, this)); + return savedVisibilityStatus.get(player.getUniqueId().toString()); + } + + /** + * Gets a Toast Notification for this Advancement + * + */ + public ToastNotification getToastNotification() { + ToastNotification notification = new ToastNotification(getDisplay().getIcon(), getDisplay().getTitle(), getDisplay().getFrame()); + return notification; + } + + /** + * Sends a Toast regardless if the Player has it in one of their Advancement Managers or not + * + * @param player Player who should see the Toast Message + */ + public void displayToast(Player player) { + ToastNotification notification = getToastNotification(); + notification.send(player); + } + + /** + * Gets an Advancement Message + * + * @param player Player who has recieved the advancement + * @return + */ + public BaseComponent getMessage(Player player) { + String translation = "chat.type.advancement." + display.getFrame().name().toLowerCase(); + boolean challenge = getDisplay().getFrame() == AdvancementFrame.CHALLENGE; + + TranslatableComponent message = new TranslatableComponent(); + message.setTranslate(translation); + TextComponent playerNameText = new TextComponent(player.getDisplayName()); + TextComponent title = new TextComponent("["); + title.addExtra(display.getTitle().getJson()); + title.addExtra("]"); + title.setColor(challenge ? ChatColor.DARK_PURPLE : ChatColor.GREEN); + BaseComponent titleTextComponent = display.getTitle().getJson(); + titleTextComponent.setColor(title.getColor()); + Text titleText = new Text(new BaseComponent[] {titleTextComponent}); + Text descriptionText = new Text(new BaseComponent[] {display.getDescription().getJson()}); + title.setHoverEvent(new HoverEvent(Action.SHOW_TEXT, titleText, new Text("\n"), descriptionText)); + message.setWith(Arrays.asList(playerNameText, title)); + + return message; + } + + /** + * Displays an Advancement Message to every Player saying Player has completed said advancement
+ * Note that this doesn't grant the advancement + * + * + * @param player Player who has recieved the advancement + */ + public void displayMessageToEverybody(Player player) { + BaseComponent message = getMessage(player); + + for(Player online : Bukkit.getOnlinePlayers()) { + online.spigot().sendMessage(ChatMessageType.CHAT, message); + } + } + + @Override + public boolean equals(Object obj) { + return getName().equals(((Advancement) obj).getName()); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/AdvancementDisplay.java b/src/eu/endercentral/crazy_advancements/advancement/AdvancementDisplay.java new file mode 100644 index 0000000..8026ece --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/AdvancementDisplay.java @@ -0,0 +1,442 @@ +package eu.endercentral.crazy_advancements.advancement; + +import javax.annotation.Nullable; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import eu.endercentral.crazy_advancements.JSONMessage; +import net.md_5.bungee.api.chat.TextComponent; +import net.minecraft.advancements.AdvancementFrameType; + +public class AdvancementDisplay { + + private ItemStack icon; + private JSONMessage title, description; + private AdvancementFrame frame; + private transient AdvancementVisibility vis; + private String backgroundTexture; + private float x = 0, y = 0; + private Advancement positionOrigin; + + //Material Constructors + + /** + * + * @param icon Icon {@link Material} + * @param title Title {@link JSONMessage} + * @param description Description {@link JSONMessage} + * @param frame {@link AdvancementFrame} + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(Material icon, JSONMessage title, JSONMessage description, AdvancementFrame frame, AdvancementVisibility visibility) { + this.icon = new ItemStack(icon); + this.title = title; + this.description = description; + this.frame = frame; + setVisibility(visibility); + } + + /** + * + * @param icon Icon {@link Material} + * @param title Title {@link String} + * @param description Description {@link String} + * @param frame {@link AdvancementFrame} + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(Material icon, String title, String description, AdvancementFrame frame, AdvancementVisibility visibility) { + this.icon = new ItemStack(icon); + TextComponent titleComponent = new TextComponent(title); + this.title = new JSONMessage(titleComponent); + this.description = new JSONMessage(new TextComponent(description)); + this.frame = frame; + setVisibility(visibility); + } + + /** + * + * @param icon Icon {@link Material} + * @param title Title {@link JSONMessage} + * @param description Description {@link JSONMessage} + * @param frame {@link AdvancementFrame} + * @param backgroundTexture Background texture path + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(Material icon, JSONMessage title, JSONMessage description, AdvancementFrame frame, String backgroundTexture, AdvancementVisibility visibility) { + this.icon = new ItemStack(icon); + this.title = title; + this.description = description; + this.frame = frame; + this.backgroundTexture = backgroundTexture; + setVisibility(visibility); + } + + /** + * + * @param icon Icon {@link Material} + * @param title Title {@link String} + * @param description Description {@link String} + * @param frame {@link AdvancementFrame} + * @param backgroundTexture Background texture path + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(Material icon, String title, String description, AdvancementFrame frame, String backgroundTexture, AdvancementVisibility visibility) { + this.icon = new ItemStack(icon); + TextComponent titleComponent = new TextComponent(title); + this.title = new JSONMessage(titleComponent); + this.description = new JSONMessage(new TextComponent(description)); + this.frame = frame; + this.backgroundTexture = backgroundTexture; + setVisibility(visibility); + } + + //ItemStack constructors + + /** + * + * @param icon Icon {@link ItemStack} + * @param title Title {@link JSONMessage} + * @param description Description {@link JSONMessage} + * @param frame {@link AdvancementFrame} + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(ItemStack icon, JSONMessage title, JSONMessage description, AdvancementFrame frame, AdvancementVisibility visibility) { + this.icon = icon; + this.title = title; + this.description = description; + this.frame = frame; + setVisibility(visibility); + } + + /** + * + * @param icon Icon {@link ItemStack} + * @param title Title {@link String} + * @param description Description {@link String} + * @param frame {@link AdvancementFrame} + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(ItemStack icon, String title, String description, AdvancementFrame frame, AdvancementVisibility visibility) { + this.icon = icon; + TextComponent titleComponent = new TextComponent(title); + this.title = new JSONMessage(titleComponent); + this.description = new JSONMessage(new TextComponent(description)); + this.frame = frame; + setVisibility(visibility); + } + + /** + * + * @param icon Icon {@link ItemStack} + * @param title Title {@link JSONMessage} + * @param description Description {@link JSONMessage} + * @param frame {@link AdvancementFrame} + * @param backgroundTexture Background texture path + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(ItemStack icon, JSONMessage title, JSONMessage description, AdvancementFrame frame, String backgroundTexture, AdvancementVisibility visibility) { + this.icon = icon; + this.title = title; + this.description = description; + this.frame = frame; + this.backgroundTexture = backgroundTexture; + setVisibility(visibility); + } + + /** + * + * @param icon Icon {@link ItemStack} + * @param title Title {@link String} + * @param description Description {@link String} + * @param frame {@link AdvancementFrame} + * @param backgroundTexture Background texture path + * @param showToast Should toast messages be shown + * @param announceChat Should advancements be announced in chat + * @param visibility When an advancement is visible + */ + public AdvancementDisplay(ItemStack icon, String title, String description, AdvancementFrame frame, String backgroundTexture, AdvancementVisibility visibility) { + this.icon = icon; + TextComponent titleComponent = new TextComponent(title); + this.title = new JSONMessage(titleComponent); + this.description = new JSONMessage(new TextComponent(description)); + this.frame = frame; + this.backgroundTexture = backgroundTexture; + setVisibility(visibility); + } + + public static enum AdvancementFrame { + + TASK(AdvancementFrameType.a), + GOAL(AdvancementFrameType.b), + CHALLENGE(AdvancementFrameType.c) + ; + + private AdvancementFrameType nms; + + private AdvancementFrame(AdvancementFrameType nms) { + this.nms = nms; + } + + public AdvancementFrameType getNMS() { + return nms; + } + + } + + /** + * + * @return Icon {@link ItemStack} + */ + public ItemStack getIcon() { + return icon; + } + + /** + * + * @return Title {@link JSONMessage} + */ + public JSONMessage getTitle() { + return title; + } + + /** + * + * @return Description {@link JSONMessage} + */ + public JSONMessage getDescription() { + return description; + } + + /** + * + * @return {@link AdvancementFrame} + */ + public AdvancementFrame getFrame() { + return frame; + } + + /** + * + * @return Background texture path + */ + @Nullable + public String getBackgroundTexture() { + return backgroundTexture; + } + + /** + * Sets the background texture + * + * @param backgroundTexture Background Texture path + */ + public void setBackgroundTexture(@Nullable String backgroundTexture) { + this.backgroundTexture = backgroundTexture; + } + + /** + * Gets the relative X coordinate + * + * @return relative X coordinate + */ + public float getX() { + return x; + } + + /** + * Gets the relative y coordinate + * + * @return relative y coordinate + */ + public float getY() { + return y; + } + + /** + * Gets the absolute x coordinate + * + * @return absolute x coordinate + */ + public float generateX() { + if(getPositionOrigin() == null) { + return x; + } else { + return getPositionOrigin().getDisplay().generateX() + x; + } + } + + /** + * Gets the absolute y coordinate + * + * @return absolute y coordinate + */ + public float generateY() { + if(getPositionOrigin() == null) { + return y; + } else { + return getPositionOrigin().getDisplay().generateY() + y; + } + } + + /** + * Gets the {@link AdvancementVisibility} + * + * @return when an advancement is visible + */ + public AdvancementVisibility getVisibility() { + return vis != null ? vis : AdvancementVisibility.VANILLA; + } + + /** + * + * @param player Player to check + * @param advancement Advancement to check (because {@link AdvancementDisplay} is not bound to one advancement) + * @return true if it should be currently visible + */ + public boolean isVisible(Player player, Advancement advancement) { + AdvancementVisibility visibility = getVisibility(); + return visibility.isVisible(player, advancement) || advancement.isGranted(player) || (visibility.isAlwaysVisibleWhenAdvancementAfterIsVisible() && advancement.isAnythingGrantedAfter(player)); + } + + /** + * + * @return the advancement that marks the origin of the coordinates + */ + public Advancement getPositionOrigin() { + return positionOrigin; + } + + + + + + /** + * Changes the Icon + * + * @param icon New Icon Material to display + */ + public void setIcon(Material icon) { + this.icon = new ItemStack(icon); + } + + /** + * Changes the Icon + * + * @param icon New Icon to display + */ + public void setIcon(ItemStack icon) { + this.icon = icon; + } + + /** + * Changes the Title + * + * @param title New title {@link JSONMessage} + */ + public void setTitle(JSONMessage title) { + this.title = title; + } + + /** + * Changes the Title + * + * @param title New Title {@link String} + */ + public void setTitle(String title) { + TextComponent titleComponent = new TextComponent(title); + this.title = new JSONMessage(titleComponent); + } + + /** + * Changes the Description + * + * @param title New title {@link JSONMessage} + */ + public void setDescription(JSONMessage description) { + this.description = description; + } + + /** + * Changes the Description + * + * @param title New Title {@link String} + */ + public void setDescription(String description) { + this.description = new JSONMessage(new TextComponent(description)); + } + + /** + * Changes the Frame + * + * @param frame New Frame + */ + public void setFrame(AdvancementFrame frame) { + this.frame = frame; + } + + /** + * Changes the visibility + * + * @param visibility New Visibility + */ + public void setVisibility(AdvancementVisibility visibility) { + this.vis = visibility; + } + + /** + * Changes the relative coordinates + * + * @param x relative x coordinate + * @param y relative y coordinate + */ + public void setCoordinates(float x, float y) { + setX(x); + setY(y); + } + + /** + * Changes the relative x coordinate + * + * @param x relative x coordinate + */ + public void setX(float x) { + this.x = x; + } + + /** + * Changes the relative y coordinate + * + * @param y relative y coordinate + */ + public void setY(float y) { + this.y = y; + } + + /** + * Changes the advancement that marks the origin of the coordinates + * + * @param positionOrigin New position origin + */ + public void setPositionOrigin(Advancement positionOrigin) { + this.positionOrigin = positionOrigin; + } + + + + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/AdvancementFlag.java b/src/eu/endercentral/crazy_advancements/advancement/AdvancementFlag.java new file mode 100644 index 0000000..636bc55 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/AdvancementFlag.java @@ -0,0 +1,13 @@ +package eu.endercentral.crazy_advancements.advancement; + +public enum AdvancementFlag { + + SHOW_TOAST, + DISPLAY_MESSAGE, + SEND_WITH_HIDDEN_BOOLEAN, + + ; + + public static final AdvancementFlag[] TOAST_AND_MESSAGE = new AdvancementFlag[] {SHOW_TOAST, DISPLAY_MESSAGE}; + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/AdvancementReward.java b/src/eu/endercentral/crazy_advancements/advancement/AdvancementReward.java new file mode 100644 index 0000000..52e677e --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/AdvancementReward.java @@ -0,0 +1,9 @@ +package eu.endercentral.crazy_advancements.advancement; + +import org.bukkit.entity.Player; + +public abstract class AdvancementReward { + + public abstract void onGrant(Player player); + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/AdvancementVisibility.java b/src/eu/endercentral/crazy_advancements/advancement/AdvancementVisibility.java new file mode 100644 index 0000000..8df36a0 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/AdvancementVisibility.java @@ -0,0 +1,97 @@ +package eu.endercentral.crazy_advancements.advancement; + +import java.util.Arrays; + +import org.bukkit.entity.Player; + +public abstract class AdvancementVisibility { + + public static final AdvancementVisibility ALWAYS = new AdvancementVisibility("ALWAYS") { + + @Override + public boolean isVisible(Player player, Advancement advancement) { + return true; + } + }, PARENT_GRANTED = new AdvancementVisibility("PARENT_GRANTED") { + + @Override + public boolean isVisible(Player player, Advancement advancement) { + if(advancement.isGranted(player)) return true; + Advancement parent = advancement.getParent(); + + return parent == null || parent.isGranted(player); + } + }, VANILLA = new AdvancementVisibility("VANILLA") { + + @Override + public boolean isVisible(Player player, Advancement advancement) { + if(advancement.isGranted(player)) return true; + + Advancement parent = advancement.getParent(); + + if(parent != null && !parent.isGranted(player)) { + Advancement grandParent = parent.getParent(); + + return grandParent == null || grandParent.getParent() == null || grandParent.isGranted(player); + } + + return true; + } + }, HIDDEN = new AdvancementVisibility("HIDDEN") { + + @Override + public boolean isVisible(Player player, Advancement advancement) { + return advancement.isGranted(player); + } + }; + + private final String name; + + public AdvancementVisibility() { + name = "CUSTOM"; + } + + private AdvancementVisibility(String name) { + this.name = name; + } + + /** + * + * @param player Player to check + * @param advancement Advancement to check + * @return true if advancement should be visible + */ + public abstract boolean isVisible(Player player, Advancement advancement); + + /** + * + * @return true if advancement should always be visible if any child should be visible, defaults to true + */ + public boolean isAlwaysVisibleWhenAdvancementAfterIsVisible() { + return true; + } + + /** + * + * @return Custom Name, only for pre-defined visibilities: {@link #ALWAYS}, {@link #PARENT_GRANTED}, {@link #VANILLA}, {@link #HIDDEN} + */ + public String getName() { + return name; + } + + /** + * Parses a visibility + * + * @param name Visibility Name + * @return A visibility with a matching {@link #getName()} or {@link #VANILLA} + */ + public static AdvancementVisibility parseVisibility(String name) { + for(AdvancementVisibility visibility : Arrays.asList(ALWAYS, PARENT_GRANTED, VANILLA, HIDDEN)) { + if(visibility.getName().equalsIgnoreCase(name)) { + return visibility; + } + } + return VANILLA; + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/ToastNotification.java b/src/eu/endercentral/crazy_advancements/advancement/ToastNotification.java new file mode 100644 index 0000000..4b66af2 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/ToastNotification.java @@ -0,0 +1,127 @@ +package eu.endercentral.crazy_advancements.advancement; + +import java.util.Arrays; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import eu.endercentral.crazy_advancements.JSONMessage; +import eu.endercentral.crazy_advancements.NameKey; +import eu.endercentral.crazy_advancements.advancement.AdvancementDisplay.AdvancementFrame; +import eu.endercentral.crazy_advancements.packet.ToastAdvancementsPacket; +import net.md_5.bungee.api.chat.TextComponent; + +public class ToastNotification { + + private final ItemStack icon; + private final JSONMessage message; + private final AdvancementFrame frame; + private final Advancement toastAdvancement; + + /** + * Constructor for creating Toast Notifications + * + * @param icon The displayed Icon + * @param message The displayed Message + * @param frame Determines the displayed Title and Sound Effect (evaluated client-side and modifiable via resource packs) + */ + public ToastNotification(ItemStack icon, JSONMessage message, AdvancementFrame frame) { + this.icon = icon; + this.message = message; + this.frame = frame; + + AdvancementDisplay toastAdvancementDisplay = new AdvancementDisplay(icon, this.message, new JSONMessage(new TextComponent("Toast Notification")), frame, AdvancementVisibility.ALWAYS); + this.toastAdvancement = new Advancement(new NameKey("eu.endercentral", "notification"), toastAdvancementDisplay, AdvancementFlag.SEND_WITH_HIDDEN_BOOLEAN); + } + + /** + * Constructor for creating Toast Notifications + * + * @param icon The displayed Icon + * @param message The displayed Message + * @param frame Determines the displayed Title and Sound Effect (evaluated client-side and modifiable via resource packs) + */ + public ToastNotification(ItemStack icon, String message, AdvancementFrame frame) { + this.icon = icon; + this.message = new JSONMessage(new TextComponent(message)); + this.frame = frame; + + AdvancementDisplay toastAdvancementDisplay = new AdvancementDisplay(icon, this.message, new JSONMessage(new TextComponent("Toast Notification")), frame, AdvancementVisibility.ALWAYS); + this.toastAdvancement = new Advancement(new NameKey("eu.endercentral", "notification"), toastAdvancementDisplay, AdvancementFlag.SEND_WITH_HIDDEN_BOOLEAN); + } + + /** + * Constructor for creating Toast Notifications + * + * @param icon The displayed Icon + * @param message The displayed Message + * @param frame Determines the displayed Title and Sound Effect (evaluated client-side and modifiable via resource packs) + */ + public ToastNotification(Material icon, JSONMessage message, AdvancementFrame frame) { + this.icon = new ItemStack(icon); + this.message = message; + this.frame = frame; + + AdvancementDisplay toastAdvancementDisplay = new AdvancementDisplay(icon, this.message, new JSONMessage(new TextComponent("Toast Notification")), frame, AdvancementVisibility.ALWAYS); + this.toastAdvancement = new Advancement(new NameKey("eu.endercentral", "notification"), toastAdvancementDisplay, AdvancementFlag.SEND_WITH_HIDDEN_BOOLEAN); + } + + /** + * Constructor for creating Toast Notifications + * + * @param icon The displayed Icon + * @param message The displayed Message + * @param frame Determines the displayed Title and Sound Effect (evaluated client-side and modifiable via resource packs) + */ + public ToastNotification(Material icon, String message, AdvancementFrame frame) { + this.icon = new ItemStack(icon); + this.message = new JSONMessage(new TextComponent(message)); + this.frame = frame; + + AdvancementDisplay toastAdvancementDisplay = new AdvancementDisplay(icon, this.message, new JSONMessage(new TextComponent("Toast Notification")), frame, AdvancementVisibility.ALWAYS); + this.toastAdvancement = new Advancement(new NameKey("eu.endercentral", "notification"), toastAdvancementDisplay, AdvancementFlag.SEND_WITH_HIDDEN_BOOLEAN); + } + + /** + * Gets the Icon + * + * @return The Icon + */ + public ItemStack getIcon() { + return icon; + } + + /** + * Gets the TItle + * + * @return The Title + */ + public JSONMessage getMessage() { + return message; + } + + /** + * Gets the Frame + * + * @return + */ + public AdvancementFrame getFrame() { + return frame; + } + + /** + * Sends this Toast Notification to a Player + * + * @param player The target Player + */ + public void send(Player player) { + toastAdvancement.getProgress(player).grant(); + ToastAdvancementsPacket addPacket = new ToastAdvancementsPacket(player, false, Arrays.asList(toastAdvancement), null); + ToastAdvancementsPacket removePacket = new ToastAdvancementsPacket(player, false, null, Arrays.asList(toastAdvancement.getName())); + + addPacket.send(); + removePacket.send(); + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/criteria/Criteria.java b/src/eu/endercentral/crazy_advancements/advancement/criteria/Criteria.java new file mode 100644 index 0000000..f66a61e --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/criteria/Criteria.java @@ -0,0 +1,109 @@ +package eu.endercentral.crazy_advancements.advancement.criteria; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import eu.endercentral.crazy_advancements.CrazyAdvancementsAPI; +import net.minecraft.advancements.Criterion; + +public class Criteria { + + private final CriteriaType type; + + private final int requiredNumber; + + private final String[] actionNames; + private final String[][] requirements; + + private final HashMap criteria = new HashMap<>(); + + /** + * Constructor for creating {@link CriteriaType.NUMBER} which will require a certain number + * + * @param requiredNumber The required number + */ + public Criteria(int requiredNumber) { + this.type = CriteriaType.NUMBER; + this.requiredNumber = requiredNumber; + this.actionNames = new String[requiredNumber]; + + for(int i = 0; i < requiredNumber; i++) { + criteria.put("" + i, CrazyAdvancementsAPI.CRITERION); + actionNames[i] = "" + i; + } + + ArrayList fixedRequirements = new ArrayList<>(); + for(String name : criteria.keySet()) { + fixedRequirements.add(new String[] {name}); + } + this.requirements = Arrays.stream(fixedRequirements.toArray()).toArray(String[][]::new); + } + + /** + * Constructor for creating {@link CriteriaType.LIST} which will require a list of actions that need to be completed
+ * For further details see Advancement/JSON Format on the Minecraft Wiki + * + * @param actionNames The names of all occuring actions + * @param requirements The definition of which and how actions are required (AND grouping of OR groups) + */ + public Criteria(String[] actionNames, String[][] requirements) { + this.type = CriteriaType.LIST; + this.requiredNumber = -1; + this.actionNames = actionNames; + this.requirements = requirements; + + for(String action : actionNames) { + criteria.put(action, CrazyAdvancementsAPI.CRITERION); + } + } + + /** + * Gets the Criteria type + * + * @return The Criteria type + */ + public CriteriaType getType() { + return type; + } + + /** + * Gets the required Number (only applies for {@link CriteriaType.NUMBER}) + * + * @return The Required Number + */ + public int getRequiredNumber() { + return requiredNumber; + } + + /** + * Gets the Action Names (auto-generated when using {@link CriteriaType.NUMBER}) + * + * @return The Actions + */ + public String[] getActionNames() { + return actionNames; + } + + /** + * Gets the Requirements (auto-generated when using {@link CriteriaType.NUMBER}) + * + * @return The Requirementsn + */ + public String[][] getRequirements() { + return requirements; + } + + /** + * Gets the generated Criteria + * + * @return The generated Criteria + */ + public HashMap getCriteria() { + return new HashMap(criteria); + } + + + + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/criteria/CriteriaType.java b/src/eu/endercentral/crazy_advancements/advancement/criteria/CriteriaType.java new file mode 100644 index 0000000..55dad73 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/criteria/CriteriaType.java @@ -0,0 +1,8 @@ +package eu.endercentral.crazy_advancements.advancement.criteria; + +public enum CriteriaType { + + LIST, + NUMBER + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/progress/AdvancementProgress.java b/src/eu/endercentral/crazy_advancements/advancement/progress/AdvancementProgress.java new file mode 100644 index 0000000..0b60b4b --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/progress/AdvancementProgress.java @@ -0,0 +1,206 @@ +package eu.endercentral.crazy_advancements.advancement.progress; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.StreamSupport; + +import net.minecraft.advancements.Criterion; +import net.minecraft.advancements.CriterionProgress; + +public class AdvancementProgress { + + private HashSet awardedCriteria = new HashSet<>(); + private net.minecraft.advancements.AdvancementProgress nmsProgress = new net.minecraft.advancements.AdvancementProgress(); + private long lastUpdate = -1; + + public AdvancementProgress(Map criteria, String[][] requirements) { + nmsProgress.a(criteria, requirements); + } + + /** + * Grants the Advancement, does not update for the player + * + * @return The result of this oepration + */ + public GenericResult grant() { + GenericResult result = GenericResult.UNCHANGED; + + Iterable missing = getNmsProgress().getRemainingCriteria(); + Iterator missingIterator = missing.iterator(); + + while(missingIterator.hasNext()) { + String next = missingIterator.next(); + CriterionProgress criterionProgress = getNmsProgress().getCriterionProgress(next); + setGranted(criterionProgress); + result = GenericResult.CHANGED; + setLastUpdate(); + } + + return result; + } + + /** + * Revokes the Advancemnt, does not update for the player + * + * @return The result of this operation + */ + public GenericResult revoke() { + GenericResult result = GenericResult.UNCHANGED; + + Iterable awarded = getNmsProgress().getAwardedCriteria(); + Iterator awardedIterator = awarded.iterator(); + long current = StreamSupport.stream(awarded.spliterator(), false).count(); + + while(current > 0 && awardedIterator.hasNext()) { + String next = awardedIterator.next(); + CriterionProgress criterionProgress = getNmsProgress().getCriterionProgress(next); + setUngranted(criterionProgress); + current--; + result = GenericResult.CHANGED; + setLastUpdate(); + } + + return result; + } + + /** + * Grants Criteria, does not update for the player + * + * @param criteria The Criteria to grant + * @return The result of this operation + */ + public GrantCriteriaResult grantCriteria(String... criteria) { + GrantCriteriaResult result = GrantCriteriaResult.UNCHANGED; + boolean doneBefore = getNmsProgress().isDone(); + + if(!doneBefore) {//Only grant criteria if the advancement is not already granted + for(String criterion : criteria) { + if(!awardedCriteria.contains(criterion)) { + CriterionProgress criterionProgress = getNmsProgress().getCriterionProgress(criterion); + if(criterionProgress != null) { + setGranted(criterionProgress); + awardedCriteria.add(criterion); + result = GrantCriteriaResult.CHANGED; + setLastUpdate(); + } + } + } + + if(getNmsProgress().isDone()) { + return GrantCriteriaResult.COMPLETED; + } + } + return result; + } + + /** + * Revokes Criteria, does not update for the player + * + * @param criteria The Criteria to revoke + * @return The result of this operation + */ + public GenericResult revokeCriteria(String... criteria) { + GenericResult result = GenericResult.UNCHANGED; + + for(String criterion : criteria) { + if(awardedCriteria.contains(criterion)) { + CriterionProgress criterionProgress = getNmsProgress().getCriterionProgress(criterion); + if(criterionProgress != null) { + setUngranted(criterionProgress); + awardedCriteria.remove(criterion); + result = GenericResult.CHANGED; + setLastUpdate(); + } + } + } + + return result; + } + + /** + * Sets Criteria, does not update for the player + * + * @param number The Criteria to set + * @return The result of this operation + */ + public SetCriteriaResult setCriteriaProgress(int number) { + SetCriteriaResult result = SetCriteriaResult.UNCHANGED; + boolean doneBefore = getNmsProgress().isDone(); + + Iterable awarded = getNmsProgress().getAwardedCriteria(); + Iterator awardedIterator = awarded.iterator(); + long current = StreamSupport.stream(awarded.spliterator(), false).count(); + + Iterable missing = getNmsProgress().getRemainingCriteria(); + Iterator missingIterator = missing.iterator(); + + while(current < number && missingIterator.hasNext()) { + String next = missingIterator.next(); + CriterionProgress criterionProgress = getNmsProgress().getCriterionProgress(next); + setGranted(criterionProgress); + current++; + result = SetCriteriaResult.CHANGED; + setLastUpdate(); + } + + while(current > number && awardedIterator.hasNext()) { + String next = awardedIterator.next(); + CriterionProgress criterionProgress = getNmsProgress().getCriterionProgress(next); + setUngranted(criterionProgress); + current--; + result = SetCriteriaResult.CHANGED; + setLastUpdate(); + } + + if(!doneBefore && getNmsProgress().isDone()) { + result = SetCriteriaResult.COMPLETED; + } + + return result; + } + + private void setGranted(CriterionProgress criterionProgress) { + criterionProgress.b(); + } + + private void setUngranted(CriterionProgress criterionProgress) { + criterionProgress.c(); + } + + /** + * Gets a list of awarded Criteria + * + * @return The list of awarded Criteria Names + */ + public HashSet getAwardedCriteria() { + return new HashSet<>(awardedCriteria); + } + + /** + * Gets the nms progress instance + * + * @return The nms progress instance + */ + public net.minecraft.advancements.AdvancementProgress getNmsProgress() { + return nmsProgress; + } + + /** + * Gets the timestamp for the last update or -1 if it has not been updated yet + * + * @return The timestamp in milliseconds or -1 if it has not been updated yet + */ + public long getLastUpdate() { + return lastUpdate; + } + + /** + * Sets the timestamp for the last update to the current system time + * + */ + public void setLastUpdate() { + lastUpdate = System.currentTimeMillis(); + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/progress/GenericResult.java b/src/eu/endercentral/crazy_advancements/advancement/progress/GenericResult.java new file mode 100644 index 0000000..a9adf13 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/progress/GenericResult.java @@ -0,0 +1,8 @@ +package eu.endercentral.crazy_advancements.advancement.progress; + +public enum GenericResult { + + UNCHANGED, + CHANGED, + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/progress/GrantCriteriaResult.java b/src/eu/endercentral/crazy_advancements/advancement/progress/GrantCriteriaResult.java new file mode 100644 index 0000000..b36c74d --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/progress/GrantCriteriaResult.java @@ -0,0 +1,9 @@ +package eu.endercentral.crazy_advancements.advancement.progress; + +public enum GrantCriteriaResult { + + UNCHANGED, + CHANGED, + COMPLETED, + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/advancement/progress/SetCriteriaResult.java b/src/eu/endercentral/crazy_advancements/advancement/progress/SetCriteriaResult.java new file mode 100644 index 0000000..34ba757 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/advancement/progress/SetCriteriaResult.java @@ -0,0 +1,10 @@ +package eu.endercentral.crazy_advancements.advancement.progress; + +public enum SetCriteriaResult { + + UNCHANGED, + CHANGED, + COMPLETED, + INVALID, + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/event/AdvancementScreenCloseEvent.java b/src/eu/endercentral/crazy_advancements/event/AdvancementScreenCloseEvent.java new file mode 100644 index 0000000..3c96908 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/event/AdvancementScreenCloseEvent.java @@ -0,0 +1,44 @@ +package eu.endercentral.crazy_advancements.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class AdvancementScreenCloseEvent extends Event { + + public static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + + private final Player player; + + public AdvancementScreenCloseEvent(Player player) { + super(true); + this.player = player; + } + + /** + * + * @return Player closing their advancement screen + */ + public Player getPlayer() { + return player; + } + + /** + * + * @return Information about this event + */ + public String getInformationString() { + return "tab_action=close;player=" + player.getName(); + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/event/AdvancementTabChangeEvent.java b/src/eu/endercentral/crazy_advancements/event/AdvancementTabChangeEvent.java new file mode 100644 index 0000000..45b7d74 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/event/AdvancementTabChangeEvent.java @@ -0,0 +1,78 @@ +package eu.endercentral.crazy_advancements.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import eu.endercentral.crazy_advancements.NameKey; + +public class AdvancementTabChangeEvent extends Event implements Cancellable { + + public static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + + private final Player player; + private NameKey tabAdvancement; + private boolean cancelled; + + public AdvancementTabChangeEvent(Player player, NameKey tabAdvancement) { + super(true); + this.player = player; + this.tabAdvancement = tabAdvancement; + } + + /** + * + * @return Player changing his advancement tab + */ + public Player getPlayer() { + return player; + } + + /** + * + * @return Tab the Player is changing to + */ + public NameKey getTabAdvancement() { + return tabAdvancement; + } + + /** + * Changes the tab the player is changing to + * + * @param tabAdvancement The new tab the player will change to + */ + public void setTabAdvancement(NameKey tabAdvancement) { + this.tabAdvancement = tabAdvancement; + } + + + /** + * + * @return Information about this event + */ + public String getInformationString() { + return "tab_action=change;player=" + player.getName() + ";tab=" + tabAdvancement.toString() + ",cancelled=" + cancelled; + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/manager/AdvancementManager.java b/src/eu/endercentral/crazy_advancements/manager/AdvancementManager.java new file mode 100644 index 0000000..070d52d --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/manager/AdvancementManager.java @@ -0,0 +1,898 @@ +package eu.endercentral.crazy_advancements.manager; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import eu.endercentral.crazy_advancements.CrazyAdvancementsAPI; +import eu.endercentral.crazy_advancements.NameKey; +import eu.endercentral.crazy_advancements.advancement.Advancement; +import eu.endercentral.crazy_advancements.advancement.AdvancementDisplay; +import eu.endercentral.crazy_advancements.advancement.AdvancementFlag; +import eu.endercentral.crazy_advancements.advancement.AdvancementReward; +import eu.endercentral.crazy_advancements.advancement.criteria.CriteriaType; +import eu.endercentral.crazy_advancements.advancement.progress.AdvancementProgress; +import eu.endercentral.crazy_advancements.advancement.progress.GenericResult; +import eu.endercentral.crazy_advancements.advancement.progress.GrantCriteriaResult; +import eu.endercentral.crazy_advancements.advancement.progress.SetCriteriaResult; +import eu.endercentral.crazy_advancements.packet.AdvancementsPacket; +import eu.endercentral.crazy_advancements.packet.PacketConverter; +import eu.endercentral.crazy_advancements.save.CriteriaData; +import eu.endercentral.crazy_advancements.save.ProgressData; +import eu.endercentral.crazy_advancements.save.SaveFile; + +public final class AdvancementManager { + + private static HashMap accessibleManagers = new HashMap<>(); + + /** + * Gets an accessible Advancement Manager by it's Name + * + * @param name + * @return the Manager or null if no matching Manager is found + */ + public static AdvancementManager getAccessibleManager(NameKey name) { + return accessibleManagers.containsKey(name.toString()) ? accessibleManagers.get(name.toString()) : null; + } + + /** + * Gets a list of all accessible Advancement Managers + * + * @return A list of all accessible Advancement Managers + */ + public static Collection getAccessibleManagers() { + return accessibleManagers.values(); + } + + private final NameKey name; + private ArrayList players; + private ArrayList advancements = new ArrayList<>(); + + /** + * Constructor for creating Advancement Managers + * + * @param players All players that should be in the new manager from the start, can be changed at any time + */ + public AdvancementManager(NameKey name, Player... players) { + this.name = name; + this.players = new ArrayList<>(); + for(Player player : players) { + this.addPlayer(player); + } + } + + /** + * Returns the Name of this Manager + * + * @return The Name + */ + public NameKey getName() { + return name; + } + + /** + * Adds a player to the manager + * + * @param player Player to add + */ + public void addPlayer(Player player) { + Validate.notNull(player); + if(!players.contains(player)) { + players.add(player); + } + + List advancements = new ArrayList<>(); + + for(Advancement advancement : getAdvancements()) { + AdvancementDisplay display = advancement.getDisplay(); + + boolean visible = display.isVisible(player, advancement); + advancement.saveVisibilityStatus(player, visible); + + if(visible) { + advancements.add(advancement); + } + } + + AdvancementsPacket packet = new AdvancementsPacket(player, false, advancements, null); + packet.send(); + } + + /** + * Removes a player from the manager + * + * @param player Player to remove + */ + public void removePlayer(Player player) { + players.remove(player); + + List removedAdvancements = new ArrayList<>(); + + for(Advancement advancement : getAdvancements()) { + removedAdvancements.add(advancement.getName()); + } + + AdvancementsPacket packet = new AdvancementsPacket(player, false, null, removedAdvancements); + packet.send(); + } + + /** + * Get a list of players in the Manager + * + * @return All players that have been added to the manager + */ + public ArrayList getPlayers() { + Iterator it = players.iterator(); + while(it.hasNext()) { + Player p = it.next(); + if(p == null || !p.isOnline()) { + it.remove(); + } + } + return players; + } + + /** + * Adds advancements to the Manager
Duplicates will be discarded + * + * @param addedAdvancements An array of all advancements that should be added + */ + public void addAdvancement(Advancement... addedAdvancements) { + List nonDuplicates = new ArrayList<>(); + for(Advancement advancement : addedAdvancements) { + if(!advancements.contains(advancement)) { + advancements.add(advancement); + nonDuplicates.add(advancement); + } + } + + HashSet updatedTabs = new HashSet<>(); + + for(Advancement adv : nonDuplicates) { + float smallestX = PacketConverter.getSmallestX(adv.getTab()); + float x = adv.getDisplay().generateX(); + if(x < smallestX) { + smallestX = x; + updatedTabs.add(adv.getTab()); + PacketConverter.setSmallestX(adv.getTab(), smallestX); + } + + float smallestY = PacketConverter.getSmallestY(adv.getTab()); + float y = adv.getDisplay().generateY(); + if(y < smallestY) { + smallestY = y; + updatedTabs.add(adv.getTab()); + PacketConverter.setSmallestY(adv.getTab(), smallestY); + } + } + + for(NameKey tab : updatedTabs) { + for(Player player : getPlayers()) { + updateTab(player, tab); + } + } + + for(Player player : getPlayers()) { + List advancements = new ArrayList<>(); + + for(Advancement advancement : nonDuplicates) { + AdvancementDisplay display = advancement.getDisplay(); + + boolean visible = display.isVisible(player, advancement); + advancement.saveVisibilityStatus(player, visible); + + if(visible) { + advancements.add(advancement); + } + } + + AdvancementsPacket packet = new AdvancementsPacket(player, false, advancements, null); + packet.send(); + } + } + + /** + * Updates advancements in this manager + * + * @param updatedAdvancements The advancements that should be updated + */ + public void updateAdvancement(Advancement... updatedAdvancements) { + Set updatedTabs = new HashSet<>(); + List remainingAdvancements = new ArrayList<>(); + List remainingNames = new ArrayList<>(); + + for(Advancement advancement : updatedAdvancements) { + boolean updated = false; + + float smallestX = PacketConverter.getSmallestX(advancement.getTab()); + float x = advancement.getDisplay().generateX(); + if(x < smallestX) { + updated = true; + smallestX = x; + updatedTabs.add(advancement.getTab()); + PacketConverter.setSmallestX(advancement.getTab(), smallestX); + } + + float smallestY = PacketConverter.getSmallestY(advancement.getTab()); + float y = advancement.getDisplay().generateY(); + if(y < smallestY) { + updated = true; + smallestY = y; + updatedTabs.add(advancement.getTab()); + PacketConverter.setSmallestY(advancement.getTab(), smallestY); + } + + if(!updated) { + remainingAdvancements.add(advancement); + } + } + + for(NameKey tab : updatedTabs) { + for(Player player : getPlayers()) { + updateTab(player, tab); + } + } + + //Update Remaining Advancements (that do not need their whole tab updated) + if(remainingAdvancements.size() > 0) { + for(Player player : getPlayers()) { + NameKey activeTab = CrazyAdvancementsAPI.getActiveTab(player); + CrazyAdvancementsAPI.clearActiveTab(player); + + AdvancementsPacket packet = new AdvancementsPacket(player, false, remainingAdvancements, remainingNames); + packet.send(); + + CrazyAdvancementsAPI.setActiveTab(player, activeTab); + } + } + } + + /** + * Removes an advancement from the Manager + * + * @param removedAdvancements An array of advancements that should be removed + */ + public void removeAdvancement(Advancement... removedAdvancements) { + List nonDuplicates = new ArrayList<>(); + for(Advancement advancement : removedAdvancements) { + if(advancements.contains(advancement)) { + advancements.remove(advancement); + nonDuplicates.add(advancement.getName()); + } + } + + for(Player player : getPlayers()) { + AdvancementsPacket packet = new AdvancementsPacket(player, false, null, nonDuplicates); + packet.send(); + } + } + + /** + * Gets a list of Advancements in the Manager + * + * @return The list of Advancements + */ + public ArrayList getAdvancements() { + return new ArrayList<>(advancements); + } + + /** + * Gets a list of Advancements with a certain namespace + * + * @param namespace Namespace to check + * @return A list of all advancements in the manager with a specified namespace + */ + public ArrayList getAdvancements(String namespace) { + ArrayList advs = getAdvancements(); + Iterator it = advs.iterator(); + while(it.hasNext()) { + Advancement adv = it.next(); + if(!adv.getName().getNamespace().equalsIgnoreCase(namespace)) { + it.remove(); + } + } + return advs; + } + + /** + * Gets an Advancement with a specified Name + * + * @param name Name to check + * @return An advancement matching the given name or null if it doesn't exist in the AdvancementManager + */ + public Advancement getAdvancement(NameKey name) { + for(Advancement advancement : advancements) { + if(advancement.hasName(name)) { + return advancement; + } + } + return null; + } + + /** + * Makes the AdvancementManager accessible to commands and other plugins using it's Name
+ * There can only be one Manager per Name + * + */ + public void makeAccessible() { + if(accessibleManagers.containsKey(name.toString())) { + throw new RuntimeException("There is already an AdvancementManager with Name '" + name + "'!"); + } else if(accessibleManagers.containsValue(this)) { + throw new RuntimeException("AdvancementManager is already accessible!"); + } + accessibleManagers.put(name.toString(), this); + } + + /** + * Resets Accessibility-Status + * + */ + public void resetAccessible() { + Iterator it = accessibleManagers.keySet().iterator(); + while(it.hasNext()) { + String name = it.next(); + if(accessibleManagers.get(name).equals(this)) { + it.remove(); + break; + } + } + } + + /** + * Updates all Advancements in a Tab
+ * If you have Advancements in different tabs + * + * @param player The target Player + * @param tab The tab to update + */ + public void updateTab(Player player, NameKey tab) { + List advancements = new ArrayList<>(); + List names = new ArrayList<>(); + for(Advancement advancement : getAdvancements()) { + if(advancement.getTab().isSimilar(tab) && advancement.getDisplay().isVisible(player, advancement)) { + advancements.add(advancement); + names.add(advancement.getName()); + } + } + NameKey activeTab = CrazyAdvancementsAPI.getActiveTab(player); + CrazyAdvancementsAPI.clearActiveTab(player); + + AdvancementsPacket packet = new AdvancementsPacket(player, false, advancements, names); + packet.send(); + + CrazyAdvancementsAPI.setActiveTab(player, activeTab); + } + + /** + * Updates Advancement Progress for a Player + * + * @param player The target Player + * @param advancements An array of Advancements that need their progress updated + */ + public void updateProgress(Player player, Advancement... advancements) { + List advancementsList = new ArrayList<>(); + for(Advancement advancement : advancements) { + boolean visible = advancement.getDisplay().isVisible(player, advancement); + advancement.saveVisibilityStatus(player, visible); + + if(visible) { + advancementsList.add(advancement);//Only send advancements that are visible + } + } + AdvancementsPacket packet = new AdvancementsPacket(player, false, advancementsList, null); + packet.send(); + } + + /** + * Updates Visibility for Advancements in this Manager + * + * @param player The target Player + */ + public void updateVisibility(Player player) { + for(Advancement advancement : getAdvancements()) { + boolean visibleBefore = advancement.getVisibilityStatus(player); + boolean visible = advancement.getDisplay().isVisible(player, advancement); + + if(visibleBefore && !visible) { + AdvancementsPacket packet = new AdvancementsPacket(player, false, null, Arrays.asList(advancement.getName())); + packet.send(); + } else if(!visibleBefore && visible) { + AdvancementsPacket packet = new AdvancementsPacket(player, false, Arrays.asList(advancement), null); + packet.send(); + } + } + } + + /** + * Grants an advancement + * + * @param player Reciever + * @param advancement Advancement to grant + * @return The Result of this operation + */ + public GenericResult grantAdvancement(Player player, Advancement advancement) { + AdvancementProgress progress = advancement.getProgress(player); + GenericResult result = progress.grant(); + + if(result == GenericResult.CHANGED) { + if(advancement.hasFlag(AdvancementFlag.SHOW_TOAST)) { + advancement.displayToast(player); + } + if(advancement.hasFlag(AdvancementFlag.DISPLAY_MESSAGE)) { + advancement.displayMessageToEverybody(player); + } + AdvancementReward reward = advancement.getReward(); + if(reward != null) { + reward.onGrant(player); + } + updateProgress(player, advancement); + } + return result; + } + + /** + * Grants an advancement, also works with offline players + * + * @param uuid Receiver UUID + * @param advancement Advancement to grant + * @return The Result of this operation + */ + public GenericResult grantAdvancement(UUID uuid, Advancement advancement) { + if(isOnline(uuid)) { + return grantAdvancement(Bukkit.getPlayer(uuid), advancement); + } else { + AdvancementProgress progress = advancement.getProgress(uuid); + GenericResult result = progress.grant(); + return result; + } + } + + /** + * Revokes an advancement + * + * @param player Receiver + * @param advancement Advancement to revoke + * @return The Result of this operation + */ + public GenericResult revokeAdvancement(Player player, Advancement advancement) { + AdvancementProgress progress = advancement.getProgress(player); + GenericResult result = progress.grant(); + + if(result == GenericResult.CHANGED) { + updateProgress(player, advancement); + } + return result; + } + + /** + * Revokes an advancement, also works with offline players + * + * @param uuid Receiver UUID + * @param advancement Advancement to revoke + * @return The Result of this operation + */ + public GenericResult revokeAdvancement(UUID uuid, Advancement advancement) { + if(isOnline(uuid)) { + return revokeAdvancement(Bukkit.getPlayer(uuid), advancement); + } else { + AdvancementProgress progress = advancement.getProgress(uuid); + GenericResult result = progress.grant(); + return result; + } + } + + /** + * Grants criteria for an advancement + * + * @param player Receiver + * @param advancement The Advancement + * @param criteria Array of criteria to grant + * @return The Result of this operation + */ + public GrantCriteriaResult grantCriteria(Player player, Advancement advancement, String... criteria) { + AdvancementProgress progress = advancement.getProgress(player); + GrantCriteriaResult result = progress.grantCriteria(criteria); + + switch(result) { + case COMPLETED: + if(advancement.hasFlag(AdvancementFlag.SHOW_TOAST)) { + advancement.displayToast(player); + } + if(advancement.hasFlag(AdvancementFlag.DISPLAY_MESSAGE)) { + advancement.displayMessageToEverybody(player); + } + AdvancementReward reward = advancement.getReward(); + if(reward != null) { + reward.onGrant(player); + } + case CHANGED: + updateProgress(player, advancement); + break; + default: + //Do nothing + } + return result; + } + + /** + * Grans criteria for an advancement, also works with offline players + * + * @param uuid Receiver + * @param advancement The Advancement + * @param criteria Array of criteria to grant + * @return The Result of this operation + */ + public GrantCriteriaResult grantCriteria(UUID uuid, Advancement advancement, String... criteria) { + if(isOnline(uuid)) { + return grantCriteria(Bukkit.getPlayer(uuid), advancement, criteria); + } else { + AdvancementProgress progress = advancement.getProgress(uuid); + GrantCriteriaResult result = progress.grantCriteria(criteria); + return result; + } + } + + /** + * Revokes criteria for an advancement + * + * @param player Receiver + * @param advancement + * @param criteria Array of criteria to revoke + * @return The Result of this operation + */ + public GenericResult revokeCriteria(Player player, Advancement advancement, String... criteria) { + AdvancementProgress progress = advancement.getProgress(player); + GenericResult result = progress.revokeCriteria(criteria); + + if(result == GenericResult.CHANGED) { + updateProgress(player, advancement); + } + + return result; + } + + /** + * Revokes criteria for an advancement, also works with offline players + * + * @param uuid Receiver + * @param advancement The Advancement + * @param criteria Array of criteria to revoke + * @return The Result of this operation + */ + public GenericResult revokeCriteria(UUID uuid, Advancement advancement, String... criteria) { + if(isOnline(uuid)) { + return revokeCriteria(Bukkit.getPlayer(uuid), advancement, criteria); + } else { + AdvancementProgress progress = advancement.getProgress(uuid); + GenericResult result = progress.revokeCriteria(criteria); + return result; + } + } + + /** + * Sets the criteria progress for an advancement
Only works for Advancements with {@link CriteriaType.NUMBER} and will return {@link SetCriteriaResult.INVALID} if it doesn't match + * + * @param player Receiver + * @param advancement The Advancement + * @param criteriaProgress Amount of progress + * @return The Result of this operation + */ + public SetCriteriaResult setCriteriaProgress(Player player, Advancement advancement, int criteriaProgress) { + if(advancement.getCriteria().getType() == CriteriaType.NUMBER) { + AdvancementProgress progress = advancement.getProgress(player); + SetCriteriaResult result = progress.setCriteriaProgress(criteriaProgress); + + switch(result) { + case COMPLETED: + if(advancement.hasFlag(AdvancementFlag.SHOW_TOAST)) { + advancement.displayToast(player); + } + if(advancement.hasFlag(AdvancementFlag.DISPLAY_MESSAGE)) { + advancement.displayMessageToEverybody(player); + } + AdvancementReward reward = advancement.getReward(); + if(reward != null) { + reward.onGrant(player); + } + case CHANGED: + updateProgress(player, advancement); + break; + default: + //Do nothing + } + + return result; + } + return SetCriteriaResult.INVALID; + } + + /** + * Sets the criteria progress for an advancement, also works with offline players
Only works for Advancements with {@link CriteriaType.NUMBER} and will return {@link SetCriteriaResult.INVALID} if it doesn't match + * + * @param uuid Receiver + * @param advancement The Advancement + * @param criteria Array of criteria to revoke + * @return The Result of this operation + */ + public SetCriteriaResult setCriteriaProgress(UUID uuid, Advancement advancement, int criteriaProgress) { + if(isOnline(uuid)) { + return setCriteriaProgress(Bukkit.getPlayer(uuid), advancement, criteriaProgress); + } else { + if(advancement.getCriteria().getType() == CriteriaType.NUMBER) { + AdvancementProgress progress = advancement.getProgress(uuid); + SetCriteriaResult result = progress.setCriteriaProgress(criteriaProgress); + + return result; + } + return SetCriteriaResult.INVALID; + } + } + + /** + * Gets the Criteria Progress + * + * @param player The target Player + * @param advancement The Advancement + * @return The criteria progress + */ + public int getCriteriaProgress(Player player, Advancement advancement) { + return advancement.getProgress(player).getAwardedCriteria().size(); + } + + /** + * Gets the Criteria Progress + * + * @param uuid The target Player + * @param advancement The Advancement + * @return The criteria progress + */ + public int getCriteriaProgress(UUID uuid, Advancement advancement) { + return advancement.getProgress(uuid).getAwardedCriteria().size(); + } + + private String getSavePath(UUID uuid) { + return CrazyAdvancementsAPI.getInstance().getDataFolder().getAbsolutePath() + File.separator + "saved_data" + File.separator + name.getNamespace() + File.separator + name.getKey() + File.separator + uuid + ".json"; + } + + private File getSaveFile(UUID uuid) { + File file = new File(getSavePath(uuid)); + file.mkdirs(); + return file; + } + + /** + * Gets the Progress as JSON + * + * @param player Player to check + * @param advancements A list of advancements that will have their progress saved- Leave empty if all Advancements should be saved + * @return A JSON String representation of the progress for a player + */ + public SaveFile createNewSave(Player player, Advancement... advancements) { + return createNewSave(player.getUniqueId(), advancements); + } + + /** + * Gets the Progress as JSON + * + * @param uuid UUID of Player to check + * @param advancements A list of advancements that will have their progress saved- Leave empty if all Advancements should be saved + * @return A JSON String representation of the progress for a player + */ + public SaveFile createNewSave(UUID uuid, Advancement... advancements) { + List advancementsList = advancements.length == 0 ? getAdvancements() : Arrays.asList(advancements); + + List progressData = new ArrayList<>(); + List criteriaData = new ArrayList<>(); + + for(Advancement advancement : advancementsList) { + switch(advancement.getCriteria().getType()) { + case NUMBER: + progressData.add(new ProgressData(advancement.getName(), getCriteriaProgress(uuid, advancement))); + break; + case LIST: + criteriaData.add(new CriteriaData(advancement.getName(), advancement.getProgress(uuid).getAwardedCriteria())); + break; + } + } + SaveFile saveFile = new SaveFile(progressData, criteriaData); + + return saveFile; + } + + /** + * Saves the progress in this Advancement Managers file + * + * @param player Player to save + * @param advancements A list of advancements that will have their progress saved- Leave empty if all Advancements should be saved + */ + public void saveProgress(Player player, Advancement... advancements) { + saveProgress(player.getUniqueId()); + } + + /** + * Saves the progress in this Advancement Managers file + * + * @param uuid UUID of Player to save + * @param advancements A list of advancements that will have their progress saved- Leave empty if all Advancements should be saved + */ + public void saveProgress(UUID uuid, Advancement... advancements) { + File file = getSaveFile(uuid); + + SaveFile saveFile = generateSaveFile(file); + SaveFile newSaveFile = createNewSave(uuid, advancements); + + saveFile.merge(newSaveFile);//Merge new Save Data onto existing Data so nothing gets lost + + try { + if(!file.exists()) { + file.createNewFile(); + } + FileWriter w = new FileWriter(file); + w.write(saveFile.toJson()); + w.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Loads the progress for Advancements in this Manager + * + * @param player Player to load + * @param advancements A list of advancements that will have their progress loaded - Leave empty if all Advancements should be loaded + */ + public void loadProgress(Player player, Advancement... advancements) { + loadProgress(player.getUniqueId(), advancements); + } + + /** + * Loads the progress for Advancements in this Manager + * + * @param uuid UUID of Player to load + * @param advancements A list of advancements that will have their progress loaded - Leave empty if all Advancements should be loaded + */ + public void loadProgress(UUID uuid, Advancement... advancements) { + File file = getSaveFile(uuid); + + SaveFile saveFile = generateSaveFile(file); + loadProgress(uuid, saveFile, advancements); + } + + /** + * Loads the progress for Advancements in this Manager + * + * @param player Player to load + * @param json The JSON data to be loaded from + * @param advancements A list of advancements that will have their progress loaded - Leave empty if all Advancements should be loaded + */ + public void loadProgress(Player player, SaveFile saveFile, Advancement... advancements) { + loadProgress(player.getUniqueId(), saveFile, advancements); + } + + /** + * Loads the progress for Advancements in this Manager + * + * @param uuid UUID of Player to load + * @param json The JSON data to be loaded from + * @param advancements A list of advancements that will have their progress loaded - Leave empty if all Advancements should be loaded + */ + public void loadProgress(UUID uuid, SaveFile saveFile, Advancement... advancements) { + List advancementsList = advancements.length == 0 ? getAdvancements() : Arrays.asList(advancements); + + for(ProgressData progressData : saveFile.getProgressData()) { + NameKey name = progressData.getName(); + int progress = progressData.getProgress(); + + for(Advancement advancement: advancementsList) { + if(advancement.hasName(name)) { + advancement.getProgress(uuid).setCriteriaProgress(progress); + break; + } + } + } + + for(CriteriaData progressData : saveFile.getCriteriaData()) { + NameKey name = progressData.getName(); + Set criteria = progressData.getCriteria(); + + for(Advancement advancement: advancementsList) { + if(advancement.hasName(name)) { + advancement.getProgress(uuid).grantCriteria(criteria.toArray(String[]::new)); + break; + } + } + } + } + + + /** + * Unloads progress for Advancements in this Manager + * + * @param player Player to unload + * @param advancements A list of advancements that will have their progress unloaded - Leave empty if all Advancements should be unloaded + */ + public void unloadProgress(Player player, Advancement... advancements) { + List advancementsList = advancements.length == 0 ? getAdvancements() : Arrays.asList(advancements); + + for(Advancement advancement : advancementsList) { + advancement.getProgress(player).revoke();//Reset Progress + updateProgress(player, advancements);//Send Resetted Progress + advancement.unloadProgress(player);//Remove Progress Object from Advancement for garbage collection + } + } + + /** + * Unloads progress for Advancements in this Manager + * + * @param uuid UUID of Player to unload + * @param advancements A list of advancements that will have their progress unloaded - Leave empty if all Advancements should be unloaded + */ + public void unloadProgress(UUID uuid, Advancement... advancements) { + List advancementsList = advancements.length == 0 ? getAdvancements() : Arrays.asList(advancements); + + for(Advancement advancement : advancementsList) { + advancement.unloadProgress(uuid);//Remove Progress Object from Advancement for garbage collection + } + } + + + + + + + + + + + + + + + + + + + + + //Internal Utility methods + + private static boolean isOnline(UUID uuid) { + Player player = Bukkit.getPlayer(uuid); + return player != null && player.isOnline(); + } + + private SaveFile generateSaveFile(File file) { + if(file.exists() && file.isFile()) { + try { + FileReader os = new FileReader(file); + + JsonParser parser = new JsonParser(); + JsonElement element = parser.parse(os); + os.close(); + + SaveFile saveFile = SaveFile.fromJSON(element); + return saveFile; + } catch (Exception ex) { + //Do nothing + } + } + throw new RuntimeException("Could not read Save File"); + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/packet/AdvancementsPacket.java b/src/eu/endercentral/crazy_advancements/packet/AdvancementsPacket.java new file mode 100644 index 0000000..d73327e --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/packet/AdvancementsPacket.java @@ -0,0 +1,117 @@ +package eu.endercentral.crazy_advancements.packet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import eu.endercentral.crazy_advancements.NameKey; +import eu.endercentral.crazy_advancements.advancement.Advancement; +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.network.protocol.game.PacketPlayOutAdvancements; +import net.minecraft.resources.MinecraftKey; + +public class AdvancementsPacket { + + private final Player player; + private final boolean reset; + private final List advancements; + private final List removedAdvancements; + + /** + * Constructor for creating Advancement Packets + * + * @param player The target Player + * @param reset Whether the Client will clear the Advancement Screen before adding the Advancements + * @param advancements A list of advancements that should be added to the Advancement Screen + * @param removedAdvancements A list of NameKeys which should be removed from the Advancement Screen + */ + public AdvancementsPacket(Player player, boolean reset, List advancements, List removedAdvancements) { + this.player = player; + this.reset = reset; + this.advancements = advancements == null ? new ArrayList<>() : new ArrayList<>(advancements); + this.removedAdvancements = removedAdvancements == null ? new ArrayList<>() : new ArrayList<>(removedAdvancements); + } + + /** + * Gets the target Player + * + * @return The target Player + */ + public Player getPlayer() { + return player; + } + + /** + * Gets whether the Client will clear the Advancement Screen before adding the Advancements + * + * @return Whether the Screen should reset + */ + public boolean isReset() { + return reset; + } + + /** + * Gets a copy of the list of the added Advancements + * + * @return The list containing the added Advancements + */ + public List getAdvancements() { + return new ArrayList<>(advancements); + } + + /** + * Gets a copy of the list of the removed Advancement's NameKeys + * + * @return The list containing the removed Advancement's NameKeys + */ + public List getRemovedAdvancements() { + return new ArrayList<>(removedAdvancements); + } + + /** + * Builds a packet that can be sent to a Player + * + * @return The Packet + */ + public PacketPlayOutAdvancements build() { + //Create Lists + List advancements = new ArrayList<>(); + Set removedAdvancements = new HashSet<>(); + Map progress = new HashMap<>(); + + //Populate Lists + for(Advancement advancement : this.advancements) { + net.minecraft.advancements.Advancement nmsAdvancement = convertAdvancement(advancement); + advancements.add(nmsAdvancement); + progress.put(advancement.getName().getMinecraftKey(), advancement.getProgress(getPlayer()).getNmsProgress()); + } + for(NameKey removed : this.removedAdvancements) { + removedAdvancements.add(removed.getMinecraftKey()); + } + + //Create Packet + PacketPlayOutAdvancements packet = new PacketPlayOutAdvancements(isReset(), advancements, removedAdvancements, progress); + return packet; + } + + protected net.minecraft.advancements.Advancement convertAdvancement(Advancement advancement) { + return PacketConverter.toNmsAdvancement(advancement); + } + + /** + * Sends the Packet to the target Player + * + */ + public void send() { + PacketPlayOutAdvancements packet = build(); + ((CraftPlayer) getPlayer()).getHandle().b.sendPacket(packet); + } + + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/packet/PacketConverter.java b/src/eu/endercentral/crazy_advancements/packet/PacketConverter.java new file mode 100644 index 0000000..685a030 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/packet/PacketConverter.java @@ -0,0 +1,120 @@ +package eu.endercentral.crazy_advancements.packet; + +import java.util.HashMap; + +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; + +import eu.endercentral.crazy_advancements.NameKey; +import eu.endercentral.crazy_advancements.advancement.Advancement; +import eu.endercentral.crazy_advancements.advancement.AdvancementDisplay; +import eu.endercentral.crazy_advancements.advancement.AdvancementFlag; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.world.item.ItemStack; + +public class PacketConverter { + + private static AdvancementRewards advancementRewards = new AdvancementRewards(0, new MinecraftKey[0], new MinecraftKey[0], null); + + private static HashMap smallestX = new HashMap<>(); + private static HashMap smallestY = new HashMap<>(); + + public static void setSmallestX(NameKey tab, float smallestX) { + PacketConverter.smallestX.put(tab, smallestX); + } + + public static float getSmallestX(NameKey key) { + return smallestX.containsKey(key) ? smallestX.get(key) : 0; + } + + public static void setSmallestY(NameKey tab, float smallestY) { + PacketConverter.smallestY.put(tab, smallestY); + } + + public static float getSmallestY(NameKey key) { + return smallestY.containsKey(key) ? smallestY.get(key) : 0; + } + + public static float generateX(NameKey tab, float displayX) { + return displayX - getSmallestX(tab); + } + + public static float generateY(NameKey tab, float displayY) { + return displayY - getSmallestY(tab); + } + + /** + * Creates an NMS Advancement + * + * @param advancement The Advancement to use as a base + * @param hidden Whether the Advancement will have it's hidden boolean set to true + * @return The NMS Advancement + */ + public static net.minecraft.advancements.Advancement toNmsAdvancement(Advancement advancement) { + AdvancementDisplay display = advancement.getDisplay(); + + ItemStack icon = CraftItemStack.asNMSCopy(display.getIcon()); + + MinecraftKey backgroundTexture = null; + boolean hasBackgroundTexture = display.getBackgroundTexture() != null; + + if(hasBackgroundTexture) { + backgroundTexture = new MinecraftKey(display.getBackgroundTexture()); + } + + float x = generateX(advancement.getTab(), display.generateX()); + float y = generateY(advancement.getTab(), display.generateY()); + + net.minecraft.advancements.AdvancementDisplay advDisplay = new net.minecraft.advancements.AdvancementDisplay(icon, display.getTitle().getBaseComponent(), display.getDescription().getBaseComponent(), backgroundTexture, display.getFrame().getNMS(), false, false, advancement.hasFlag(AdvancementFlag.SEND_WITH_HIDDEN_BOOLEAN)); + advDisplay.a(x, y); + + net.minecraft.advancements.Advancement parent = advancement.getParent() == null ? null : createDummy(advancement.getParent().getName()); + net.minecraft.advancements.Advancement adv = new net.minecraft.advancements.Advancement(advancement.getName().getMinecraftKey(), parent, advDisplay, advancementRewards, advancement.getCriteria().getCriteria(), advancement.getCriteria().getRequirements()); + + return adv; + } + + /** + * Creates an NMS Toast Advancement + * + * @param advancement The Advancement to use as a base + * @param hidden Whether the Advancement will have it's hidden boolean set to true + * @return The NMS Advancement + */ + public static net.minecraft.advancements.Advancement toNmsToastAdvancement(Advancement advancement) { + AdvancementDisplay display = advancement.getDisplay(); + + ItemStack icon = CraftItemStack.asNMSCopy(display.getIcon()); + + MinecraftKey backgroundTexture = null; + boolean hasBackgroundTexture = display.getBackgroundTexture() != null; + + if(hasBackgroundTexture) { + backgroundTexture = new MinecraftKey(display.getBackgroundTexture()); + } + + float x = generateX(advancement.getTab(), display.generateX()); + float y = generateY(advancement.getTab(), display.generateY()); + + net.minecraft.advancements.AdvancementDisplay advDisplay = new net.minecraft.advancements.AdvancementDisplay(icon, display.getTitle().getBaseComponent(), display.getDescription().getBaseComponent(), backgroundTexture, display.getFrame().getNMS(), true, false, advancement.hasFlag(AdvancementFlag.SEND_WITH_HIDDEN_BOOLEAN)); + advDisplay.a(x, y); + + net.minecraft.advancements.Advancement parent = advancement.getParent() == null ? null : createDummy(advancement.getParent().getName()); + net.minecraft.advancements.Advancement adv = new net.minecraft.advancements.Advancement(advancement.getName().getMinecraftKey(), parent, advDisplay, advancementRewards, advancement.getCriteria().getCriteria(), advancement.getCriteria().getRequirements()); + + return adv; + } + + /** + * Creates a Dummy Advancement
Internally used to generate temporary parent advancements that need to be referenced in the packet + * + * @param name The name of the Advancement + * @return the Dummy Advancement + */ + public static net.minecraft.advancements.Advancement createDummy(NameKey name) { +// net.minecraft.advancements.AdvancementDisplay advDisplay = new net.minecraft.advancements.AdvancementDisplay(null, null, null, null, AdvancementFrameType.a, false, false, false); + net.minecraft.advancements.Advancement adv = new net.minecraft.advancements.Advancement(name.getMinecraftKey(), null, null, null, new HashMap<>(), new String[0][0]); + return adv; + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/packet/ToastAdvancementsPacket.java b/src/eu/endercentral/crazy_advancements/packet/ToastAdvancementsPacket.java new file mode 100644 index 0000000..2ed138b --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/packet/ToastAdvancementsPacket.java @@ -0,0 +1,30 @@ +package eu.endercentral.crazy_advancements.packet; + +import java.util.List; + +import org.bukkit.entity.Player; + +import eu.endercentral.crazy_advancements.NameKey; +import eu.endercentral.crazy_advancements.advancement.Advancement; + +public class ToastAdvancementsPacket extends AdvancementsPacket { + + /** + * Constructor for creating Toast Advancement Packets + * + * @param player The target Player + * @param reset Whether the Client will clear the Advancement Screen before adding the Advancements + * @param advancements A list of advancements that should be added to the Advancement Screen + * @param removedAdvancements A list of NameKeys which should be removed from the Advancement Screen + */ + public ToastAdvancementsPacket(Player player, boolean reset, List advancements, List removedAdvancements) { + super(player, reset, advancements, removedAdvancements); + } + + @Override + protected net.minecraft.advancements.Advancement convertAdvancement(Advancement advancement) { + return PacketConverter.toNmsToastAdvancement(advancement); + } + + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/save/CriteriaData.java b/src/eu/endercentral/crazy_advancements/save/CriteriaData.java new file mode 100644 index 0000000..df5c402 --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/save/CriteriaData.java @@ -0,0 +1,25 @@ +package eu.endercentral.crazy_advancements.save; + +import java.util.Set; + +import eu.endercentral.crazy_advancements.NameKey; + +public class CriteriaData { + + private final NameKey name; + private final Set criteria; + + public CriteriaData(NameKey name, Set criteria) { + this.name = name; + this.criteria = criteria; + } + + public NameKey getName() { + return name; + } + + public Set getCriteria() { + return criteria; + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/save/ProgressData.java b/src/eu/endercentral/crazy_advancements/save/ProgressData.java new file mode 100644 index 0000000..82b9ade --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/save/ProgressData.java @@ -0,0 +1,23 @@ +package eu.endercentral.crazy_advancements.save; + +import eu.endercentral.crazy_advancements.NameKey; + +public class ProgressData { + + private final NameKey name; + private final int progress; + + public ProgressData(NameKey name, int progress) { + this.name = name; + this.progress = progress; + } + + public NameKey getName() { + return name; + } + + public int getProgress() { + return progress; + } + +} \ No newline at end of file diff --git a/src/eu/endercentral/crazy_advancements/save/SaveFile.java b/src/eu/endercentral/crazy_advancements/save/SaveFile.java new file mode 100644 index 0000000..3e544ad --- /dev/null +++ b/src/eu/endercentral/crazy_advancements/save/SaveFile.java @@ -0,0 +1,103 @@ +package eu.endercentral.crazy_advancements.save; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; + +import eu.endercentral.crazy_advancements.NameKey; + +public class SaveFile { + + private static final Gson gson = new Gson(); + + private final Map progressData; + private final Map criteriaData; + + /** + * Constructor for creating a Save File + * + * @param data A list of Advancement Progress that is saved by progress number + * @param criteriaData A list of Advancement Progress that is saved by criteria list + */ + public SaveFile(List progressData, List criteriaData) { + this.progressData = new HashMap<>(); + for(ProgressData progress : progressData) { + this.progressData.put(progress.getName(), progress); + } + + this.criteriaData = new HashMap<>(); + for(CriteriaData criteria : criteriaData) { + this.criteriaData.put(criteria.getName(), criteria); + } + } + + /** + * Gets a list of Advancement Progress that is saved by progress number + * + * @return The list containing {@link ProgressData} + */ + public Collection getProgressData() { + return progressData.values(); + } + + /** + * Gets a list of Advancement Progress that is saved by criteria list + * + * @return The list containing {@link CriteriaData} + */ + public Collection getCriteriaData() { + return criteriaData.values(); + } + + /** + * Merges another Save File onto this one
+ * The Save File that is merged will take priority + * + * @param saveFile The Save File that should be merged into this one + */ + public void merge(SaveFile saveFile) { + //Merge Progress Data + for(ProgressData progress : saveFile.getProgressData()) { + this.progressData.put(progress.getName(), progress); + } + + //Merge Criteria Data + for(CriteriaData criteria : saveFile.getCriteriaData()) { + this.criteriaData.put(criteria.getName(), criteria); + } + } + + /** + * Converts this Save File to JSON + * + * @return + */ + public String toJson() { + return gson.toJson(this); + } + + /** + * Creates a Save File from JSON Input + * + * @param json The Input JSON + * @return The newly created Save File + */ + public static SaveFile fromJSON(String json) { + return gson.fromJson(json, SaveFile.class); + } + + /** + * Creates a Save File from JSON Input + * + * @param json The Input JSON + * @return The newly created Save File + */ + public static SaveFile fromJSON(JsonElement json) { + return gson.fromJson(json, SaveFile.class); + } + +} \ No newline at end of file