diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e012065 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index fc52ce1..de0c600 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,18 @@ src/main/resources true + + **/*.ogg + **/*.png + + + + src/main/resources + false + + **/*.ogg + **/*.png + diff --git a/src/main/java/me/zenox/evocraft/EvoCraft.java b/src/main/java/me/zenox/evocraft/EvoCraft.java index dd0f236..6b27ac5 100644 --- a/src/main/java/me/zenox/evocraft/EvoCraft.java +++ b/src/main/java/me/zenox/evocraft/EvoCraft.java @@ -17,6 +17,7 @@ import me.zenox.evocraft.enchant.EnchantRegistry; import me.zenox.evocraft.events.*; import me.zenox.evocraft.item.ItemRegistry; +import me.zenox.evocraft.item.PackGenerator; import me.zenox.evocraft.item.VanillaItem; import me.zenox.evocraft.network.GlowFilter; import me.zenox.evocraft.recipe.RecipeRegistry; @@ -42,6 +43,7 @@ public final class EvoCraft extends JavaPlugin { private static ProtocolManager protocolManager; private static ChapterManager chapterManager; private static ActionBar actionBar; + private static PackGenerator packGenerator; public static EvoCraft getPlugin() { return plugin; @@ -102,6 +104,8 @@ public void onEnable() { registerListeners(); + packGenerator = new PackGenerator("pack"); + } private void registerListeners() { @@ -170,9 +174,14 @@ public static ChapterManager getChapterManager() { public static ActionBar getActionBar() { return actionBar; } + + public static PackGenerator getPackGenerator() { + return packGenerator; + } + public void reload() { this.reloadConfig(); - this.languageLoader = new LanguageLoader(this); + languageLoader = new LanguageLoader(this); } @Override diff --git a/src/main/java/me/zenox/evocraft/abilities/ItemAbility.java b/src/main/java/me/zenox/evocraft/abilities/ItemAbility.java index 610ecd6..2d5f42f 100644 --- a/src/main/java/me/zenox/evocraft/abilities/ItemAbility.java +++ b/src/main/java/me/zenox/evocraft/abilities/ItemAbility.java @@ -1069,9 +1069,14 @@ public void run() { // Remove the metadata player.removeMetadata("thunderstrike_projectile", EvoCraft.getPlugin()); } - // Create particles at the projectile's location - player.getWorld().spawnParticle(Particle.CRIT_MAGIC, projectile.getLocation(), 10, 0.3, 0.3, 0.3, 0.1); - player.getWorld().spawnParticle(Particle.REDSTONE, projectile.getLocation(), 10, 0.3, 0.3, 0.3, 0.1, new Particle.DustOptions(Color.fromRGB(244, 252, 3), 1)); + //loop through blocks until you find the highest block that is not air + Location loc = projectile.getLocation(); + while (loc.getBlock().getType() == Material.AIR) { + loc.add(0, 1, 0); + } + //create lighting at the block + player.getWorld().spawnParticle(Particle.CRIT_MAGIC, loc, 10, 0.3, 0.3, 0.3, 0.1); + player.getWorld().spawnParticle(Particle.REDSTONE, loc, 10, 0.3, 0.3, 0.3, 0.1, new Particle.DustOptions(Color.fromRGB(244, 252, 3), 1)); } }.runTaskTimer(EvoCraft.getPlugin(), 0, 3); } diff --git a/src/main/java/me/zenox/evocraft/command/Command.java b/src/main/java/me/zenox/evocraft/command/Command.java index 07f1319..67f72d4 100644 --- a/src/main/java/me/zenox/evocraft/command/Command.java +++ b/src/main/java/me/zenox/evocraft/command/Command.java @@ -12,6 +12,12 @@ import me.zenox.evocraft.story.Chapter; import me.zenox.evocraft.story.ChapterManager; import me.zenox.evocraft.util.Util; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.command.CommandExecutor; @@ -225,6 +231,76 @@ public boolean onCommand(CommandSender sender, org.bukkit.command.Command comman EvoCraft.getChapterManager().setChapter(player, chapter); return true; } + case "pack" -> { + if (args.length < 3) { + Util.sendMessage(sender, "Please specify a valid resource pack URL and SHA-1 hash."); + return true; + } + + // verify that the URL is valid + if (!Util.urlIsValid(args[1])) { + Util.sendMessage(sender, "Please specify a valid resource pack URL."); + return true; + } + + // set the URL of the resource pack + EvoCraft.getPackGenerator().setUrl(args[1]); + // set in config + plugin.getConfig().set("resource-pack-url", args[1]); + + // verify and set the SHA-1 hash + String sha1HashString = args[2]; + + try { + // Convert the hex string to a byte array + byte[] sha1Hash = Util.hexStringToByteArray(sha1HashString); + EvoCraft.getPackGenerator().setHash(sha1Hash); + plugin.getConfig().set("resource-pack-sha1", sha1HashString); + } catch (IllegalArgumentException e) { + Util.sendMessage(sender, "The SHA-1 hash provided is not a valid hexadecimal string."); + return true; + } + + // send confirmation message + Util.sendMessage(sender, "&aResource pack URL set to " + args[1]); + Util.sendMessage(sender, "&aSHA-1 hash set to " + sha1HashString); + + // send funny message with clickable text + Util.sendMessage(sender, + Component.text().content("CLICK HERE TO APPLY THE RESOURCE PACK FOR ALL PLAYERS") + .color(NamedTextColor.YELLOW) + .decorate(TextDecoration.BOLD) + .decorate(TextDecoration.UNDERLINED) + .hoverEvent(HoverEvent.showText(Component.text("Run /evocraft applypack ").color(NamedTextColor.GRAY))) + .clickEvent(ClickEvent.suggestCommand("/evocraft applypack")) + .build()); + plugin.saveConfig(); + return true; + } + case "applypack" -> { + // apply the resource pack to all players + for (Player player : Bukkit.getOnlinePlayers()) { + EvoCraft.getPackGenerator().applyPack(player); + } + Util.sendMessage(sender, "&aResource pack applied to all players."); + return true; + } + case "modeldata" -> { + // get custommodeldata of item in hand + if (sender instanceof Player) { + Player player = (Player) sender; + ItemStack item = player.getInventory().getItemInMainHand(); + if (item.getType() == Material.AIR) { + Util.sendMessage(sender, ChatColor.WHITE + "This item has no CustomModelData (that is created by EvoCraft)"); + return true; + } + Util.sendMessage(sender, "The CustomModelData of " + item.getItemMeta().getDisplayName() + "&f is " + ComplexItemStack.of(item).getComplexItem().getCustomModelData()); + Util.sendMessage(sender, "Actual model data: " + item.getItemMeta().getCustomModelData()); + return true; + } else { + Util.sendMessage(sender, "You must be a player to use this command!"); + } + } default -> Util.sendMessage(sender, "EvoCraft Help Page."); } return true; @@ -243,6 +319,9 @@ public List 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: + *

+ *

+ * 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 ]