onTabComplete(CommandSender sender, org.bukkit.command.Comma
arguments.add("enchant");
arguments.add("reload");
arguments.add("removechapterdata");
+ arguments.add("pack");
+ arguments.add("applypack");
+ arguments.add("modeldata");
}
if (items.isEmpty()) {
diff --git a/src/main/java/me/zenox/evocraft/item/PackGenerator.java b/src/main/java/me/zenox/evocraft/item/PackGenerator.java
new file mode 100644
index 0000000..82bb612
--- /dev/null
+++ b/src/main/java/me/zenox/evocraft/item/PackGenerator.java
@@ -0,0 +1,601 @@
+package me.zenox.evocraft.item;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.ticxo.modelengine.api.ModelEngineAPI;
+import me.zenox.evocraft.EvoCraft;
+import me.zenox.evocraft.util.ItemUtils;
+import me.zenox.evocraft.util.Util;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.security.CodeSource;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+
+/**
+ * Resource pack generator that is used to generate resource packs for the plugin
+ *
+ * Users should place item files in `assets/textures/items` in the plugin's data folder
+ *
+ * Embedded in the JAR is a default set of PNG files that will be copied to the data folder if it doesn't exist, as well as a template
+ */
+public class PackGenerator implements Listener {
+
+ private static final Pattern PNG_PATTERN = Pattern.compile("(.+?)(?:_\\d+)?\\.png");
+ public static final String TEXTURE_LOCATION = "assets/evocraft/textures/item/";
+ public static final String MODEL_LOCATION = "assets/evocraft/models/item/";
+ public static final String VANILLA_LOCATION = "assets/minecraft/models/item/";
+ private final String targetPath;
+ private String url;
+ private byte[] hash;
+ private File resourcePack;
+
+ public PackGenerator(String targetPath) {
+ this.targetPath = targetPath;
+ this.resourcePack = createPack();
+ this.url = EvoCraft.getPlugin().getConfig().getString("resource-pack-url");
+ this.hash = generateHash(resourcePack);
+
+ Bukkit.getPluginManager().registerEvents(this, EvoCraft.getPlugin());
+
+ if ("FILL IN".equals(this.url)) {
+ new BukkitRunnable(){
+
+ @Override
+ public void run() {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ if (player.isOp() && "FILL IN".equals(url)) {
+ Util.sendMessage(player, "&cResource pack URL not set in config! Set with /evocraft pack ");
+ }
+ }
+ }
+ }.runTaskTimer(EvoCraft.getPlugin(), 0, 4000);
+ EvoCraft.getPlugin().getLogger().warning("Resource pack URL not set in config! Set with /evocraft pack ");
+ }
+ }
+
+ public File createPack() {
+ // Location of the png files for items, this is where devs/users will add them
+ File pngDirectory = new File(EvoCraft.getPlugin().getDataFolder(), "assets/textures/items");
+ HashMap> map = readPngs(pngDirectory);
+
+ // Next, we want to compile a new resource pack folder
+ // We can use a bare-bones template with all the required files and folders and simply copy it
+ // For a first prototype, we'll only modify item files and textures, and deal with sounds + MEG later on
+ resourcePack = new File(EvoCraft.getPlugin().getDataFolder(), targetPath);
+ // delete the old resource pack folder
+ if (resourcePack.exists()) {
+ try {
+ Util.logToConsole("Regenerating resource pack.");
+ // delete the directory and all files inside it
+ Files.walk(Paths.get(resourcePack.getAbsolutePath()))
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ copyTemplatePack(resourcePack);
+
+ // Now we need to copy the PNG files into the resource pack
+ copyPNGToPack(map);
+
+ // Clone modelEngine files
+ File modelEngine = new File(ModelEngineAPI.api.getDataFolder(), "resource pack");
+ copyModelEngineFiles(modelEngine, resourcePack);
+
+ // Afterward, we need to compile the ComplexItem JSON files and add them to the resource pack
+ compileComplexItemJSON(map);
+
+ // Then, we need to compile the vanilla item JSON files (add overrides) and add them to the resource pack
+ compileVanillaItemJSON(map);
+
+ // Finally, we need to zip the resource pack folder and save it as a .zip file
+ resourcePack = zipResourcePack(resourcePack);
+
+ return resourcePack;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public static void deepCopyFolder(File from, File to, StandardCopyOption option){
+ to.mkdirs();
+ for (File sub: Objects.requireNonNull(from.listFiles())) {
+ if (sub.isDirectory()) {
+ deepCopyFolder(sub, new File(to, sub.getName()), option);
+ } else {
+ try {
+ Files.copy(sub.toPath(), new File(to, sub.getName()).toPath(), option);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Method to read PNG files from a directory and associate them with ComplexItems by ID
+ * @param directory the directory to read PNG files from
+ * @return a HashMap of ComplexItems and their associated PNG files
+ */
+ private HashMap> readPngs(File directory) {
+ HashMap> map = new HashMap<>();
+
+ if (!directory.exists()) {
+ directory.mkdirs();
+ copyDefaultPNGs(directory);
+ }
+
+ for (File file : directory.listFiles()) {
+ if (file.isDirectory()) {
+ map.putAll(readPngs(file));
+ } else if (isPng(file)) {
+ String itemName = extractItemName(file.getName());
+ ComplexItem item = ItemRegistry.byId(itemName);
+ if (item != null) {
+ map.computeIfAbsent(item, k -> new ArrayList<>()).add(file);
+ } else {
+ Util.logToConsole("ComplexItem not found for " + itemName);
+ }
+ } else {
+ Util.logToConsole("File " + file.getName() + " is not a PNG file");
+ }
+ }
+ return map;
+ }
+
+ private boolean isPng(File file) {
+ return file.getName().toLowerCase().endsWith(".png");
+ }
+
+ private String extractItemName(String fileName) {
+ Matcher matcher = PNG_PATTERN.matcher(fileName);
+ if (matcher.matches()) {
+ return matcher.group(1).toLowerCase();
+ }
+ return null;
+ }
+
+ /**
+ * Copy the default PNG files from the JAR to the specified directory
+ *
+ * Notice that this copies from the embedded JAR, not the plugin's data folder,
+ * and that this is purely for the purpose of providing a default set of PNG files (think of it for "other" users)
+ * @param directory the directory to copy PNG files to
+ */
+ private void copyDefaultPNGs(File directory) {
+ // Scan through JAR entries to find PNGs in the specified directory
+ try {
+ CodeSource src = EvoCraft.class.getProtectionDomain().getCodeSource();
+ if (src != null) {
+ int count = 0;
+ URL jar = src.getLocation();
+ try (ZipInputStream zip = new ZipInputStream(jar.openStream())) {
+ ZipEntry ze = null;
+ while ((ze = zip.getNextEntry()) != null) {
+ String entryName = ze.getName();
+ if (entryName.startsWith("assets/textures/items") && entryName.endsWith(".png")) {
+ // Found a PNG, now copy it
+ count++;
+ File outFile = new File(directory, new File(entryName).getName());
+ if (!outFile.exists()) {
+ try (InputStream in = EvoCraft.getPlugin().getResource(entryName)) {
+ Files.copy(in, outFile.toPath());
+ }
+ }
+ }
+ }
+ }
+ Util.logToConsole("Copied " + count + " PNG files from JAR");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Copies an embedded template resource pack folder to the specified directory
+ * Look at the copyDefaultPNGs method for reference
+ * @param directory the directory to copy to
+ * @return the resulting resource pack folder
+ */
+ private File copyTemplatePack(File directory) {
+ String resourcePath = "assets/pack_template";
+
+ try {
+ URL dirURL = EvoCraft.getPlugin().getClass().getClassLoader().getResource(resourcePath);
+ if (dirURL != null && dirURL.getProtocol().equals("jar")) {
+ JarURLConnection jarConnection = (JarURLConnection) dirURL.openConnection();
+ JarFile jar = jarConnection.getJarFile();
+ Enumeration entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (name.startsWith(resourcePath)) {
+ String entryPath = name.substring(resourcePath.length() + 1); // +1 to remove the leading slash
+ File outputFile = new File(directory, entryPath);
+ if (!entry.isDirectory()) {
+ try (InputStream in = EvoCraft.getPlugin().getClass().getClassLoader().getResourceAsStream(name)) {
+ Files.copy(in, outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ } else {
+ outputFile.mkdirs();
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to copy template pack", e);
+ }
+
+ return directory; // Return the directory where the files have been copied
+ }
+
+
+ /**
+ * Copies PNG files to the resource pack folder
+ *
+ * The path of the files in the resource pack folder should be `assets/evocraft/textures/items`
+ * @param map a HashMap of ComplexItems and their associated PNG files
+ */
+ private void copyPNGToPack(HashMap> map) {
+ for (Map.Entry> entry : map.entrySet()) {
+ for (File file : entry.getValue()) {
+ try {
+ Files.copy(file.toPath(), new File(resourcePack, TEXTURE_LOCATION + file.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Copies the ModelEngine files to the resource pack folder
+ *
+ * Copies ALL contents of:
+ *
+ * - assets/modelengine
+ * - assets/modelengine/models/... (there are many folders for all the different entities)
+ * - assets/modelengine/textures/entity (there are many files for all the different entities)
+ * - assets/minecraft
+ * - assets/minecraft/models/item/leather_horse_armor.json
+ *
+ *
+ * Recursively does a deep copy on the needed ModelEngine folders, copying files into preexisting folders if necessary.
+ *
+ * Note: It may be beneficial to write a recursive algorithm that copies all files from one directory to another,
+ * maintaining directory structure and previous files, as it will be more versatile and you will not need to fix your code if ModelEngine updates
+ * @param modelEngine the ModelEngine resource pack folder
+ * @param resourcePack the resource pack folder
+ */
+ private void copyModelEngineFiles(File modelEngine, File resourcePack) {
+ File assetsFolder = new File(modelEngine, "assets");
+ if(!assetsFolder.exists()){
+ throw new RuntimeException("ModelEngine resource pack folder does not exist");
+ }
+
+ // copy item model to resource pack
+ File itemModel = new File(assetsFolder, "minecraft/models/item/leather_horse_armor.json");
+ File itemModelDest = new File(resourcePack, "assets/minecraft/models/item/leather_horse_armor.json");
+ itemModelDest.mkdirs();
+ try {
+ Files.copy(itemModel.toPath(), itemModelDest.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ // copy modelengine files
+ // textures first
+ File texturesFolder = new File(assetsFolder, "modelengine/textures/entity");
+
+ deepCopyFolder(texturesFolder, new File(resourcePack, "assets/modelengine/textures/entity"), StandardCopyOption.REPLACE_EXISTING);
+
+ // then, copy models which have subfolders
+ File modelsFolder = new File(assetsFolder, "modelengine/models");
+ deepCopyFolder(modelsFolder, new File(resourcePack, "assets/modelengine/models"), StandardCopyOption.REPLACE_EXISTING);
+
+ }
+
+ /**
+ *
+ * Creates and compiles the ComplexItem model JSON files,
+ * these are not the files that override the vanilla files, but rather the files that define the model of the JSON itself.
+ *
+ * They exist at {@code assets/minecraft/models/item/}
+ *
+ * A example file might look like this:
+ * {@code enchanted_magma_block.json}
+ *
+ * {
+ * "parent": "minecraft:item/generated",
+ * "textures": {
+ * "layer0": "evocraft:item/enchanted_magma_block"
+ * }
+ * }
+ *
+ * Make sure to research what the different parent items are, as it's pertinent to how the item is displayed in-game. You'll reference these when generating your vanilla JSON files.
+ *
+ *
+ * Make sure to research writing to JSON and output streams.
+ *
+ * @param itemFiles a HashMap of ComplexItems and their associated PNG files
+ */
+ private void compileComplexItemJSON(Map> itemFiles) {
+ File dir = new File(EvoCraft.getPlugin().getDataFolder(), targetPath + File.separator + MODEL_LOCATION);
+ dir.mkdirs();
+
+ for (Map.Entry> entry : itemFiles.entrySet()) {
+ ComplexItem item = entry.getKey();
+ List files = entry.getValue();
+
+ for (int i = 0; i < files.size(); i++) {
+ File file = files.get(i);
+ String variantId = item.getId() + (i > 0 ? "_" + i : ""); // Adjust ID for variants
+
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("parent", "minecraft:item/" + (ItemUtils.isHandheld(item.getMaterial()) ? "handheld" : "generated"));
+ JsonObject textures = new JsonObject();
+ textures.addProperty("layer0", "evocraft:item/" + variantId);
+ jsonObject.add("textures", textures);
+
+ // Save the JSON file using variantId
+ saveJSONFile(MODEL_LOCATION, jsonObject, variantId);
+ }
+ }
+ }
+
+ private void saveJSONFile(String location, JsonObject jsonObject, String itemId) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ try {
+ // Construct the file path
+ String filePath = EvoCraft.getPlugin().getDataFolder() + File.separator + targetPath + File.separator + location + itemId + ".json";
+ File file = new File(filePath);
+
+ // Create the file and directories if they do not exist
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+
+ // Write the JSON content to the file
+ try (FileWriter fileWriter = new FileWriter(file)) {
+ fileWriter.write(gson.toJson(jsonObject));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ *
+ * Creates and compiles the vanilla model files that override the vanilla files, with overrides for CustomModelData.
+ *
+ * These files also exist at {@code assets/minecraft/models/item/}
+ * This is marginally more difficult than implementing the ComplexItem JSON generator, as there exist multiple ComplexItems for each VanillaItem.
+ *
+ * I have gone ahead and compiled a map of Material -> {@code List},
+ * so that for every material, you simply have to get Minecraft's default template and then add the overrides for each ComplexItem.
+ *
+ *
+ * A example file might look like this:
+ * {@code blaze_rod.json}
+ *
+ * {
+ * "parent": "minecraft:item/handheld",
+ * "textures": {
+ * "layer0": "minecraft:item/blaze_rod"
+ * },
+ * "overrides": [
+ * {
+ * "predicate": {
+ * "custom_model_data": 2673713
+ * },
+ * "model": "minecraft:item/fiery_ember_staff"
+ * },
+ * {
+ * "predicate": {
+ * "custom_model_data": 3240166
+ * },
+ * "model": "minecraft:item/enchanted_blaze_rod"
+ * },
+ * {
+ * "predicate": {
+ * "custom_model_data": 4732991
+ * },
+ * "model": "minecraft:item/dark_ember_staff"
+ * },
+ * {
+ * "predicate": {
+ * "custom_model_data": 4732992
+ * },
+ * "model": "minecraft:item/dark_ember_staff_2"
+ * }
+ * ]
+ * }
+ * }
+ *
+ * You should be able to fetch the CustomModelData of the item via the ComplexItem#getCustomModelData() method of each ComplexItem.
+ *
+ */
+ private void compileVanillaItemJSON(Map> itemFiles) {
+ File directory = new File(EvoCraft.getPlugin().getDataFolder(), targetPath + File.separator + VANILLA_LOCATION);
+ directory.mkdirs();
+
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ Map> materialItemsMap = organizeItemsByMaterial(new ArrayList<>(itemFiles.keySet()));
+
+ for (Map.Entry> entry : materialItemsMap.entrySet()) {
+ String material = entry.getKey();
+ JsonObject rootObject = createRootJsonObject(material);
+ JsonArray overrides = createOverridesArray(entry.getValue(), itemFiles);
+ rootObject.add("overrides", overrides);
+ saveJSONFile(VANILLA_LOCATION, rootObject, material);
+ }
+ }
+
+ private Map> organizeItemsByMaterial(List items) {
+ Map> materialItemsMap = new HashMap<>();
+ for (ComplexItem item : items) {
+ String materialKey = item.getMaterial().toString().toLowerCase();
+ materialItemsMap.computeIfAbsent(materialKey, k -> new ArrayList<>()).add(item);
+ }
+ return materialItemsMap;
+ }
+
+ private JsonObject createRootJsonObject(String material) {
+ JsonObject rootObject = new JsonObject();
+ rootObject.addProperty("parent", "minecraft:item/" + getItemType(material));
+ JsonObject textures = new JsonObject();
+ textures.addProperty("layer0", "minecraft:item/" + material);
+ rootObject.add("textures", textures);
+ return rootObject;
+ }
+
+ private String getItemType(String material) {
+ return ItemUtils.isHandheld(Material.valueOf(material.toUpperCase())) ? "handheld" : "generated";
+ }
+
+ private JsonArray createOverridesArray(List items, Map> itemFiles) {
+ JsonArray overrides = new JsonArray();
+ for (ComplexItem item : items) {
+ List variants = itemFiles.get(item);
+ for (int i = 0; i < variants.size(); i++) {
+ JsonObject predicate = new JsonObject();
+ predicate.addProperty("custom_model_data", item.getCustomModelData() + i); // Adjusting custom model data for variants
+
+ JsonObject override = new JsonObject();
+ override.add("predicate", predicate);
+ String modelId = "evocraft:item/" + item.getId() + (i > 0 ? "_" + i : ""); // Adjusting model ID for variants
+ override.addProperty("model", modelId);
+ overrides.add(override);
+ }
+ }
+ return overrides;
+ }
+
+ /**
+ * Zip and save the resource pack folder into the resource pack directory. Make sure you zip the contents of the folder, not the folder itself, as that will cause issues.
+ * @param directory the directory to zip
+ */
+ private File zipResourcePack(@NotNull File directory) {
+ // Zip the directory
+ // Save the zip file to the resource pack directory
+ try {
+ //input file zip path
+ pack(directory.getAbsolutePath(), directory.getAbsolutePath() + ".zip");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return new File(directory.getAbsolutePath() + ".zip");
+ }
+
+ private static void pack(String sourceDirPath, String zipFilePath) throws IOException {
+ Path p = Paths.get(zipFilePath);
+ if (Files.exists(p)) {
+ Files.delete(p);
+ }
+
+ Files.createFile(p);
+ try (ZipOutputStream zs = new ZipOutputStream(Files.newOutputStream(p))) {
+ Path pp = Paths.get(sourceDirPath);
+ Files.walk(pp)
+ .filter(path -> !Files.isDirectory(path))
+ .forEach(path -> {
+ ZipEntry zipEntry = new ZipEntry(pp.relativize(path).toString());
+ try {
+ zs.putNextEntry(zipEntry);
+ Files.copy(path, zs);
+ zs.closeEntry();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Generate a sha1 hash given the resource pack file
+ * @return a byte array containing the sha1 hash
+ */
+ private byte[] generateHash(File resourcePack) {
+ try {
+ // Create an instance of MessageDigest for SHA-1
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+
+ // Create a FileInputStream to read the resource pack file
+ try (FileInputStream fis = new FileInputStream(resourcePack)) {
+ byte[] byteArray = new byte[1024];
+ int bytesCount;
+
+ // Read the file data and update the MessageDigest
+ while ((bytesCount = fis.read(byteArray)) != -1) {
+ digest.update(byteArray, 0, bytesCount);
+ }
+ }
+
+ // Complete the hash computation
+ return digest.digest();
+
+ } catch (NoSuchAlgorithmException | IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public void applyPack(Player player){
+ String message = "Applying resource pack...";
+ Util.sendMessage(player, message);
+ // Set the resource pack for the player
+ player.setResourcePack(this.url, this.hash, Component.text("Please install this resource pack in order to properly" +
+ " render server models. You will be disconnected upon rejection of the pack."), true);
+ }
+
+ /**
+ * Sets the resource pack for a player when they join the server
+ */
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event){
+ if ("FILL IN".equals(this.url)) return; // If the URL is not set, don't do anything
+
+ applyPack(event.getPlayer());
+ }
+
+ public String getSha1() {
+ return Base64.getEncoder().encodeToString(this.hash);
+ }
+
+ public void setHash(byte[] hash) {
+ this.hash = hash;
+ }
+}
diff --git a/src/main/java/me/zenox/evocraft/util/ItemUtils.java b/src/main/java/me/zenox/evocraft/util/ItemUtils.java
new file mode 100644
index 0000000..186f3bb
--- /dev/null
+++ b/src/main/java/me/zenox/evocraft/util/ItemUtils.java
@@ -0,0 +1,30 @@
+package me.zenox.evocraft.util;
+
+import org.bukkit.Material;
+import java.util.Set;
+import java.util.EnumSet;
+
+public class ItemUtils {
+ private static final Set HANDHELD_ITEMS = EnumSet.of(
+ // Tools
+ Material.WOODEN_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.DIAMOND_AXE, Material.NETHERITE_AXE,
+ Material.WOODEN_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.DIAMOND_PICKAXE, Material.NETHERITE_PICKAXE,
+ Material.WOODEN_SHOVEL, Material.STONE_SHOVEL, Material.IRON_SHOVEL, Material.DIAMOND_SHOVEL, Material.NETHERITE_SHOVEL,
+ Material.WOODEN_HOE, Material.STONE_HOE, Material.IRON_HOE, Material.DIAMOND_HOE, Material.NETHERITE_HOE,
+
+ // Weapons
+ Material.WOODEN_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.DIAMOND_SWORD, Material.NETHERITE_SWORD,
+ Material.BOW, Material.CROSSBOW, Material.TRIDENT,
+
+ // rod like items
+ Material.BLAZE_ROD, Material.STICK,
+
+ // Others
+ Material.FISHING_ROD, Material.CARROT_ON_A_STICK, Material.WARPED_FUNGUS_ON_A_STICK,
+ Material.SHEARS
+ );
+
+ public static boolean isHandheld(Material material) {
+ return HANDHELD_ITEMS.contains(material);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/zenox/evocraft/util/Util.java b/src/main/java/me/zenox/evocraft/util/Util.java
index fd7ca4d..d46bb0d 100644
--- a/src/main/java/me/zenox/evocraft/util/Util.java
+++ b/src/main/java/me/zenox/evocraft/util/Util.java
@@ -8,6 +8,8 @@
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import me.zenox.evocraft.EvoCraft;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextComponent;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
@@ -18,6 +20,7 @@
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
+import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -30,10 +33,22 @@ public static void sendMessage(Player p, String message) {
sendMessage(p, ChatColor.translateAlternateColorCodes('&', message), true);
}
+ public static void sendMessage(Player p, TextComponent message) {
+ sendMessage(p, message, true);
+ }
+
+ public static void sendMessage(CommandSender p, TextComponent message) {
+ sendMessage(p, message, true);
+ }
+
public static void sendMessage(@NotNull Player p, String message, boolean prefix) {
p.sendMessage(ChatColor.translateAlternateColorCodes('&', prefix ? ChatColor.AQUA + "[Evo" + ChatColor.GREEN + "Craft] " + ChatColor.WHITE + message : ChatColor.WHITE + message));
}
+ public static void sendMessage(CommandSender p, TextComponent message, boolean prefix) {
+ p.sendMessage(prefix ? Component.text(ChatColor.AQUA + "[Evo" + ChatColor.GREEN + "Craft] ").append(message) : message);
+ }
+
public static void sendMessage(CommandSender p, String message) {
sendMessage(p, ChatColor.translateAlternateColorCodes('&', message), true);
}
@@ -162,4 +177,38 @@ public static double round(double value, int digits) {
double factor = Math.pow(10, digits);
return Math.round(value * factor) / factor;
}
+
+ /**
+ * Checks if a given string is a valid url
+ * @param url
+ * @return
+ */
+ public static boolean urlIsValid(String url) {
+ try {
+ URI.create(url).toURL().openStream().close();
+ return true;
+ }
+ catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Converts a hex string to a byte array.
+ *
+ * @param hexString The string containing hexadecimal digits.
+ * @return A byte array corresponding to the hex string.
+ */
+ public static byte[] hexStringToByteArray(String hexString) {
+ if (hexString.length() % 2 != 0) {
+ throw new IllegalArgumentException("Invalid hex string.");
+ }
+ byte[] bytes = new byte[hexString.length() / 2];
+ for (int i = 0; i < bytes.length; i++) {
+ int index = i * 2;
+ int value = Integer.parseInt(hexString.substring(index, index + 2), 16);
+ bytes[i] = (byte) value;
+ }
+ return bytes;
+ }
}
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/backstory_1.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/backstory_1.ogg
new file mode 100644
index 0000000..7239f73
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/backstory_1.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/backstory_2.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/backstory_2.ogg
new file mode 100644
index 0000000..89f328a
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/backstory_2.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/money_drop.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/money_drop.ogg
new file mode 100644
index 0000000..454e599
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/money_drop.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/player_death.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/player_death.ogg
new file mode 100644
index 0000000..09dddfc
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/player_death.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/startup.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/startup.ogg
new file mode 100644
index 0000000..a51a259
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/startup.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_hurt.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_hurt.ogg
new file mode 100644
index 0000000..a0f1f1c
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_hurt.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle.ogg
new file mode 100644
index 0000000..cc9aa88
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle_vocal.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle_vocal.ogg
new file mode 100644
index 0000000..a43f9aa
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle_vocal.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle_vocal_01.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle_vocal_01.ogg
new file mode 100644
index 0000000..7dbf26b
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_idle_vocal_01.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave.ogg
new file mode 100644
index 0000000..cd1bacd
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave_01.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave_01.ogg
new file mode 100644
index 0000000..6fc270a
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave_01.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave_projectile.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave_projectile.ogg
new file mode 100644
index 0000000..ccb815c
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shockwave_projectile.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shoot.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shoot.ogg
new file mode 100644
index 0000000..6105f74
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shoot.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shoot_vocal.ogg b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shoot_vocal.ogg
new file mode 100644
index 0000000..034e5a3
Binary files /dev/null and b/src/main/resources/assets/pack_template/assets/evocraft/sounds/custom/wildfire_shoot_vocal.ogg differ
diff --git a/src/main/resources/assets/pack_template/assets/evocraft/textures/item/filler.json b/src/main/resources/assets/pack_template/assets/evocraft/textures/item/filler.json
new file mode 100644
index 0000000..b9d3956
--- /dev/null
+++ b/src/main/resources/assets/pack_template/assets/evocraft/textures/item/filler.json
@@ -0,0 +1,3 @@
+{
+ "what is this?": "this is a filler file to ensure that the directory structure is properly copied for resource pack generation. it may be ignored."
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/pack_template/assets/minecraft/atlases/blocks.json b/src/main/resources/assets/pack_template/assets/minecraft/atlases/blocks.json
new file mode 100644
index 0000000..ae9cadc
--- /dev/null
+++ b/src/main/resources/assets/pack_template/assets/minecraft/atlases/blocks.json
@@ -0,0 +1 @@
+{"sources":[{"source":"entity","prefix":"entity/","type":"directory"}]}
\ No newline at end of file
diff --git a/src/main/resources/assets/pack_template/assets/minecraft/sounds.json b/src/main/resources/assets/pack_template/assets/minecraft/sounds.json
new file mode 100644
index 0000000..3e99e61
--- /dev/null
+++ b/src/main/resources/assets/pack_template/assets/minecraft/sounds.json
@@ -0,0 +1,41 @@
+{
+ "entity.wildfire.hurt": {
+ "sounds":["evocraft:custom/wildfire_hurt"]
+ },
+ "entity.wildfire.idle": {
+ "sounds":["evocraft:custom/wildfire_idle"]
+ },
+ "entity.wildfire.idle_vocal": {
+ "sounds":["evocraft:custom/wildfire_idle_vocal",
+ "evocraft:custom/wildfire_idle_vocal_01"]
+ },
+ "entity.wildfire.shoot": {
+ "sounds":["evocraft:custom/wildfire_shoot"]
+ },
+ "entity.wildfire.shoot_vocal": {
+ "sounds":["evocraft:custom/wildfire_shoot_vocal"]
+ },
+ "entity.wildfire.shockwave": {
+ "sounds":["evocraft:custom/wildfire_shockwave",
+ "evocraft:custom/wildfire_shockwave_01"]
+ },
+ "entity.wildfire.shockwave_projectile": {
+ "sounds":["evocraft:custom/wildfire_shockwave_projectile"]
+ },
+ "entity.player.retro_death": {
+ "sounds":["evocraft:custom/player_death"]
+ },
+ "entity.player.death_coins": {
+ "sounds":["evocraft:custom/money_drop"]
+ },
+ "story.0.startup": {
+ "sounds":["evocraft:custom/startup"]
+ },
+ "story.chapter.1.backstory_1": {
+ "sounds":["evocraft:custom/backstory_1"]
+ },
+ "story.chapter.1.backstory_2": {
+ "sounds":["evocraft:custom/backstory_2"]
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/pack_template/pack.mcmeta b/src/main/resources/assets/pack_template/pack.mcmeta
new file mode 100644
index 0000000..4e2ece9
--- /dev/null
+++ b/src/main/resources/assets/pack_template/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "pack_format": 8,
+ "description": "Pre-zipped Model Engine Resource Pack"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/pack_template/pack.png b/src/main/resources/assets/pack_template/pack.png
new file mode 100644
index 0000000..0782441
Binary files /dev/null and b/src/main/resources/assets/pack_template/pack.png differ
diff --git a/src/main/resources/assets/textures/items/blaziel.png b/src/main/resources/assets/textures/items/blaziel.png
new file mode 100644
index 0000000..4866977
Binary files /dev/null and b/src/main/resources/assets/textures/items/blaziel.png differ
diff --git a/src/main/resources/assets/textures/items/blazielpillar.png b/src/main/resources/assets/textures/items/blazielpillar.png
new file mode 100644
index 0000000..0a0bb0a
Binary files /dev/null and b/src/main/resources/assets/textures/items/blazielpillar.png differ
diff --git a/src/main/resources/assets/textures/items/crucified_amulet.png b/src/main/resources/assets/textures/items/crucified_amulet.png
new file mode 100644
index 0000000..cc8d48f
Binary files /dev/null and b/src/main/resources/assets/textures/items/crucified_amulet.png differ
diff --git a/src/main/resources/assets/textures/items/dark_ember_staff.png b/src/main/resources/assets/textures/items/dark_ember_staff.png
new file mode 100644
index 0000000..2a6163e
Binary files /dev/null and b/src/main/resources/assets/textures/items/dark_ember_staff.png differ
diff --git a/src/main/resources/assets/textures/items/dark_ember_staff_2.png b/src/main/resources/assets/textures/items/dark_ember_staff_2.png
new file mode 100644
index 0000000..774557c
Binary files /dev/null and b/src/main/resources/assets/textures/items/dark_ember_staff_2.png differ
diff --git a/src/main/resources/assets/textures/items/desecrator_scale.png b/src/main/resources/assets/textures/items/desecrator_scale.png
new file mode 100644
index 0000000..92d6567
Binary files /dev/null and b/src/main/resources/assets/textures/items/desecrator_scale.png differ
diff --git a/src/main/resources/assets/textures/items/enchanted_blaze_rod.png b/src/main/resources/assets/textures/items/enchanted_blaze_rod.png
new file mode 100644
index 0000000..0569d8b
Binary files /dev/null and b/src/main/resources/assets/textures/items/enchanted_blaze_rod.png differ
diff --git a/src/main/resources/assets/textures/items/enchanted_magma_block.png b/src/main/resources/assets/textures/items/enchanted_magma_block.png
new file mode 100644
index 0000000..006af8a
Binary files /dev/null and b/src/main/resources/assets/textures/items/enchanted_magma_block.png differ
diff --git a/src/main/resources/assets/textures/items/fiery_ember_staff.png b/src/main/resources/assets/textures/items/fiery_ember_staff.png
new file mode 100644
index 0000000..6796f3f
Binary files /dev/null and b/src/main/resources/assets/textures/items/fiery_ember_staff.png differ
diff --git a/src/main/resources/assets/textures/items/greatsword_volkumos.png b/src/main/resources/assets/textures/items/greatsword_volkumos.png
new file mode 100644
index 0000000..63b073e
Binary files /dev/null and b/src/main/resources/assets/textures/items/greatsword_volkumos.png differ
diff --git a/src/main/resources/assets/textures/items/magic_toy_stick.png b/src/main/resources/assets/textures/items/magic_toy_stick.png
new file mode 100644
index 0000000..e95df83
Binary files /dev/null and b/src/main/resources/assets/textures/items/magic_toy_stick.png differ
diff --git a/src/main/resources/assets/textures/items/pythemion_gem.png b/src/main/resources/assets/textures/items/pythemion_gem.png
new file mode 100644
index 0000000..621cfd5
Binary files /dev/null and b/src/main/resources/assets/textures/items/pythemion_gem.png differ
diff --git a/src/main/resources/assets/textures/items/tormented_blade.png b/src/main/resources/assets/textures/items/tormented_blade.png
new file mode 100644
index 0000000..27be6d7
Binary files /dev/null and b/src/main/resources/assets/textures/items/tormented_blade.png differ
diff --git a/src/main/resources/assets/textures/items/volken_axe.png b/src/main/resources/assets/textures/items/volken_axe.png
new file mode 100644
index 0000000..1bde58b
Binary files /dev/null and b/src/main/resources/assets/textures/items/volken_axe.png differ
diff --git a/src/main/resources/assets/textures/items/volken_axe_item.png b/src/main/resources/assets/textures/items/volken_axe_item.png
new file mode 100644
index 0000000..3b519ec
Binary files /dev/null and b/src/main/resources/assets/textures/items/volken_axe_item.png differ
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index e41ef3c..0ae297c 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -4,4 +4,6 @@
force_update_default: true
# Locale
-locale: en_US
\ No newline at end of file
+locale: en_US
+
+resource-pack-url: "FILL IN"
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 6368357..c6496ef 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -7,6 +7,7 @@ description: Transforms your game into an highly specialized MMORPG experience.
softdepend: [ Citizens, WorldGuard, WorldEdit ]
depend: [ AureliumSkills, ProtocolLib, ModelEngine, Vault ]
+
commands:
evocraft:
aliases: [ evc, ec, evo ]