diff --git a/build.gradle b/build.gradle index 4722aec..56eda2e 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'fr.openmc.riftengine' -version = '0.1-SNAPSHOT' +version = '0.2-SNAPSHOT' repositories { mavenCentral() diff --git a/gradle.properties b/gradle.properties index e00b769..28f79ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # NMS & paper lib -paperWeightUserDevVersion = 2.0.0-beta.22 +paperWeightUserDevVersion = 2.0.0-beta.23 # Paper version minecraftVersion = 26.2 diff --git a/libs/OpenMC.jar b/libs/OpenMC.jar index 1b2f6e9..425d23d 100644 Binary files a/libs/OpenMC.jar and b/libs/OpenMC.jar differ diff --git a/src/main/java/fr/openmc/riftengine/core/RiftPlugin.java b/src/main/java/fr/openmc/riftengine/core/RiftPlugin.java index ed81202..cc2dd3b 100644 --- a/src/main/java/fr/openmc/riftengine/core/RiftPlugin.java +++ b/src/main/java/fr/openmc/riftengine/core/RiftPlugin.java @@ -1,10 +1,14 @@ package fr.openmc.riftengine.core; +import fr.openmc.core.CommandsManager; import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.riftengine.core.commands.GlyphCommand; import fr.openmc.riftengine.core.converter.ConverterManager; +import fr.openmc.riftengine.core.listeners.LoadAfterItemsAdderListener; import fr.openmc.riftengine.core.registry.glyphs.GlyphsRegistry; import lombok.Getter; import lombok.Setter; +import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.event.Event; import org.geysermc.geyser.api.GeyserApi; @@ -16,6 +20,8 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; public class RiftPlugin extends JavaPlugin implements EventRegistrar { @@ -28,9 +34,13 @@ public class RiftPlugin extends JavaPlugin implements EventRegistrar { @Getter private RiftConfig riftConfig; + public final Path CONFIG_FOLDER = getDataFolder().toPath().resolve("config"); - private ConverterManager converterManager; + private final List commands = new ArrayList<>(List.of( + new GlyphCommand() + )); + private ConverterManager converterManager; public static final int[] RP_VERSION = new int[] {1,0}; @Override @@ -51,10 +61,18 @@ public void onEnable() { // * Listeners registerEvent(SessionLoadResourcePacksEvent.class, this::onLoadResourcePacks); + registerListener( + new LoadAfterItemsAdderListener() + ); + + // * Commands + for (Object command : commands) { + CommandsManager.getHandler().register(command); + } GlyphsRegistry glyphsRegistry = RiftRegistry.GLYPHS; OMCLogger.info("RiftEngine activé!"); - OMCLogger.infoFormatted(glyphsRegistry.size() + "/" + glyphsRegistry.maxSize() + "glyphs enregistré"); + OMCLogger.infoFormatted(glyphsRegistry.size() + "/" + glyphsRegistry.maxSize() + " glyphs enregistré"); OMCLogger.infoFormatted("Glyphs : " + glyphsRegistry.values()); } @@ -83,7 +101,12 @@ public void onLoadResourcePacks(SessionLoadResourcePacksEvent event) { event.register(pack, PriorityOption.HIGHEST); - OMCLogger.successFormatted("RiftEngine: pack registered !"); + OMCLogger.successFormatted("RiftEngine: pack enregistré !"); + } + + public void registerListener(Listener... listeners) { + for (Listener listener : listeners) + this.getServer().getPluginManager().registerEvents(listener, this); } public void registerEvent(Class listenerClass, Consumer handler) { diff --git a/src/main/java/fr/openmc/riftengine/core/RiftRegistry.java b/src/main/java/fr/openmc/riftengine/core/RiftRegistry.java index a2089db..c38bd63 100644 --- a/src/main/java/fr/openmc/riftengine/core/RiftRegistry.java +++ b/src/main/java/fr/openmc/riftengine/core/RiftRegistry.java @@ -7,6 +7,7 @@ import fr.openmc.core.bootstrap.registries.RegistryLoadingType; import fr.openmc.riftengine.core.registry.glyphs.GlyphsRegistry; import fr.openmc.riftengine.core.registry.mapping.MappingRegistry; +import fr.openmc.riftengine.core.registry.scanner.ScannerRegistry; import java.util.ArrayList; import java.util.Arrays; @@ -16,6 +17,7 @@ public final class RiftRegistry { // * Registre globaux public static MappingRegistry MAPPINGS; public static GlyphsRegistry GLYPHS; + public static ScannerRegistry SCANNERS; private static final List LOADED = new ArrayList<>(); @@ -25,6 +27,9 @@ public final class RiftRegistry { RegistryLoadingType.RUNTIME), new RegistryContext( () -> GLYPHS = new GlyphsRegistry(), + RegistryLoadingType.RUNTIME), + new RegistryContext( + () -> SCANNERS = new ScannerRegistry(), RegistryLoadingType.RUNTIME) ); diff --git a/src/main/java/fr/openmc/riftengine/core/commands/GlyphCommand.java b/src/main/java/fr/openmc/riftengine/core/commands/GlyphCommand.java new file mode 100644 index 0000000..88292df --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/commands/GlyphCommand.java @@ -0,0 +1,24 @@ +package fr.openmc.riftengine.core.commands; + +import fr.openmc.riftengine.core.RiftRegistry; +import fr.openmc.riftengine.core.commands.autocomplete.GlyphAutocomplete; +import fr.openmc.shaded.revxrsal.commands.annotation.Command; +import fr.openmc.shaded.revxrsal.commands.annotation.Subcommand; +import fr.openmc.shaded.revxrsal.commands.annotation.SuggestWith; +import fr.openmc.shaded.revxrsal.commands.bukkit.annotation.CommandPermission; +import org.bukkit.entity.Player; + +@Command({"glyph", "remoji"}) +@CommandPermission("omc.commands.rift.glyph") +public class GlyphCommand { + @Subcommand("test") + @CommandPermission("omc.commands.rift.glyph.set") + public void set( + Player player, + @SuggestWith(GlyphAutocomplete.class) String namespacedId + ) { + if (RiftRegistry.GLYPHS.get(namespacedId).isEmpty()) return; + + player.sendMessage("caractere mis : " + RiftRegistry.GLYPHS.get(namespacedId).get().getBedrockChar()); + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/commands/autocomplete/GlyphAutocomplete.java b/src/main/java/fr/openmc/riftengine/core/commands/autocomplete/GlyphAutocomplete.java new file mode 100644 index 0000000..2f14de7 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/commands/autocomplete/GlyphAutocomplete.java @@ -0,0 +1,21 @@ +package fr.openmc.riftengine.core.commands.autocomplete; + +import fr.openmc.riftengine.core.RiftRegistry; +import fr.openmc.riftengine.core.registry.glyphs.Glyph; +import fr.openmc.shaded.revxrsal.commands.autocomplete.SuggestionProvider; +import fr.openmc.shaded.revxrsal.commands.bukkit.actor.BukkitCommandActor; +import fr.openmc.shaded.revxrsal.commands.node.ExecutionContext; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class GlyphAutocomplete implements SuggestionProvider { + + @Override + public @NotNull List getSuggestions(@NotNull ExecutionContext context) { + return RiftRegistry.GLYPHS.values() + .stream() + .map(Glyph::getNamespacedId) + .toList(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/riftengine/core/converter/ConverterManager.java b/src/main/java/fr/openmc/riftengine/core/converter/ConverterManager.java index acb0aa5..bcbb5df 100644 --- a/src/main/java/fr/openmc/riftengine/core/converter/ConverterManager.java +++ b/src/main/java/fr/openmc/riftengine/core/converter/ConverterManager.java @@ -1,10 +1,13 @@ package fr.openmc.riftengine.core.converter; import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.core.utils.FilesUtils; import fr.openmc.riftengine.core.RiftConfig; import fr.openmc.riftengine.core.RiftPlugin; import fr.openmc.riftengine.core.converter.writers.PackWriter; -import fr.openmc.riftengine.core.converter.writers.font.FontWriter; +import fr.openmc.riftengine.core.converter.writers.glyph.font.FontWriter; +import fr.openmc.riftengine.core.converter.writers.glyph.icons.IconsWriter; +import fr.openmc.riftengine.core.converter.writers.glyph.icons.SymbolWriter; import fr.openmc.riftengine.core.converter.writers.manifest.IconWriter; import fr.openmc.riftengine.core.converter.writers.manifest.ManifestWriter; import fr.openmc.riftengine.core.converter.writers.manifest.PackIdentity; @@ -20,6 +23,7 @@ import java.util.ArrayList; import java.util.List; +// todo: faire un converter manager clean, avec des systemes de step, (surtout du coté logger aussi) public class ConverterManager { private final RiftPlugin plugin; @@ -30,6 +34,7 @@ public class ConverterManager { public ConverterManager(RiftPlugin plugin) { this.plugin = plugin; RiftConfig config = plugin.getRiftConfig(); + Path itemsAdderContents = getItemsAdderContents(plugin); try { identity = PackIdentity.loadOrCreate(plugin); @@ -38,7 +43,9 @@ public ConverterManager(RiftPlugin plugin) { new ManifestWriter(identity), new TranslationInjector(), new FontWriter(), - new ScoreboardUiWriter(config.isHideScoreboardNumberBedrock()) + new ScoreboardUiWriter(config.isHideScoreboardNumberBedrock()), + new IconsWriter(itemsAdderContents), + new SymbolWriter() )); } catch (Exception e) { throw new RuntimeException("Erreur lors d'initialisation du ConverterManager", e); @@ -52,6 +59,7 @@ public Path generateConvertedPack() throws IOException { Path javaPackPath = getJavaPackPath(RiftPlugin.getInstance()); Path outputDir = plugin.getDataFolder().toPath().resolve("output"); + FilesUtils.deleteDirectory(outputDir.toFile()); Files.createDirectories(outputDir); Path workDir = outputDir.resolve("internal"); @@ -81,4 +89,12 @@ public static Path getJavaPackPath(JavaPlugin plugin) { return generatedDir.toPath(); } + + public static Path getItemsAdderContents(JavaPlugin plugin) { + File pluginsDir = plugin.getDataFolder().getParentFile(); // * root/plugins/ + File itemsAdderDir = new File(pluginsDir, "ItemsAdder"); // * root/plugins/ItemsAdder + File contentDir = new File(itemsAdderDir, "contents"); // * root/plugins/ItemsAdder/output + + return contentDir.toPath(); + } } diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/font/FontChar.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/font/FontChar.java similarity index 94% rename from src/main/java/fr/openmc/riftengine/core/converter/writers/font/FontChar.java rename to src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/font/FontChar.java index d58d0b1..cc34ac4 100644 --- a/src/main/java/fr/openmc/riftengine/core/converter/writers/font/FontChar.java +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/font/FontChar.java @@ -1,4 +1,4 @@ -package fr.openmc.riftengine.core.converter.writers.font; +package fr.openmc.riftengine.core.converter.writers.glyph.font; import com.google.gson.JsonArray; diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/font/FontWriter.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/font/FontWriter.java similarity index 84% rename from src/main/java/fr/openmc/riftengine/core/converter/writers/font/FontWriter.java rename to src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/font/FontWriter.java index d83d95f..d61bb87 100644 --- a/src/main/java/fr/openmc/riftengine/core/converter/writers/font/FontWriter.java +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/font/FontWriter.java @@ -1,4 +1,4 @@ -package fr.openmc.riftengine.core.converter.writers.font; +package fr.openmc.riftengine.core.converter.writers.glyph.font; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -50,15 +50,17 @@ public void write(Path bedrockRootPath, Path javaRootPath) throws IOException { String file = IdentifierUtils.normalizeId(provider.get("file").getAsString()); fontNamespace = file.split(":")[0]; - fontId = file.split(":")[1] - .replace("font/", "") - .replace(".png", ""); - fontPng = javaRootPath - .resolve("assets") - .resolve(file.split(":")[0]) - .resolve("textures") - .resolve(file.split(":")[1]) - .toFile(); + + if (file.split(":").length == 2) { + fontId = file.split(":")[1] + .replace("font/", "") + .replace(".png", ""); + } else { + fontId = file + .replace("font/", "") + .replace(".png", ""); + } + fontPng = IdentifierUtils.resolveTextureId(javaRootPath, file).toFile(); fontsChar = provider.getAsJsonArray("chars"); } } diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsRegrouperUtils.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsRegrouperUtils.java new file mode 100644 index 0000000..65e732b --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsRegrouperUtils.java @@ -0,0 +1,21 @@ +package fr.openmc.riftengine.core.converter.writers.glyph.icons; + +public class IconsRegrouperUtils { + + private final static int[] GROUP_SIZE = {8, 16, 20, 24, 32, 48, 64, 96, 128, 192, 256, 512}; + + /** + * Utilitaire pour choisir la meilleur dimension dans lequel mettre l'emoji/icons + * @param maxDim la dimension maximale que l'on peut mettre + * @return la dimension la plus proche de maxDim + */ + public static int pickBestSize(int maxDim) { + for (int size : GROUP_SIZE) { + if (maxDim <= size) { + return size; + } + } + + return GROUP_SIZE[GROUP_SIZE.length - 1]; + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsType.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsType.java new file mode 100644 index 0000000..09aa602 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsType.java @@ -0,0 +1,115 @@ +package fr.openmc.riftengine.core.converter.writers.glyph.icons; + +import fr.openmc.riftengine.core.scanner.icons.IconEntry; + +import java.awt.*; + +public enum IconsType { + PREFIX { + @Override + public boolean matches(int width, int height) { + double aspectRatio = (double) Math.max(width, height) / Math.min(width, height); + return aspectRatio > 2.0; + } + + @Override + public Dimension finalSize(IconEntry entry, int width, int height) { + return new Dimension(width, height); // pas de resize + } + + @Override + public int yOffset(IconEntry entry, int height) { + return 0; + } + }, + + MENU_GUI { + @Override + public boolean matches(int width, int height) { + return Math.max(width, height) > 128; + } + + @Override + public Dimension finalSize(IconEntry entry, int width, int height) { + return new Dimension(0, 0); + } + + @Override + public int yOffset(IconEntry entry, int height) { + return 0; + } + }, + + ICON { + @Override + public boolean matches(int width, int height) { + return false; // à detecter manuellement via config/icons_type.yml + } + + @Override + public Dimension finalSize(IconEntry entry, int width, int height) { + double scale = entry.scaleRatio() / (double) height; + int w = Math.max(1, (int) Math.round(width * scale)); + int h = Math.max(1, (int) Math.round(height * scale)); + return new Dimension(w, h); + } + + @Override + public int yOffset(IconEntry entry, int height) { + return entry.yPosition() != null ? -entry.yPosition() : DEFAULT_Y_OFFSET; + } + }, + + EMOJI { + @Override + public boolean matches(int width, int height) { + int maxDim = Math.max(width, height); + return maxDim <= 128 && maxDim > 0; + } + + @Override + public Dimension finalSize(IconEntry entry, int width, int height) { + double scale = yOffset(entry, height) / (double) height; + int w = Math.max(1, (int) Math.round(width * scale)); + int h = Math.max(1, (int) Math.round(height * scale)); + return new Dimension(w, h); + } + + @Override + public int yOffset(IconEntry entry, int height) { + return entry.yPosition() != null ? entry.yPosition() : DEFAULT_Y_OFFSET; + } + }, + + EMOJI_16 { + @Override + public boolean matches(int width, int height) { + return false; // a detecter manuellement via config/icons_type.yml + } + + @Override + public Dimension finalSize(IconEntry entry, int width, int height) { + return new Dimension(16, 16); + } + + @Override + public int yOffset(IconEntry entry, int height) { + return entry.yPosition() != null ? entry.yPosition() : DEFAULT_Y_OFFSET; + } + }; + + private static final int DEFAULT_Y_OFFSET = 9; + + public abstract boolean matches(int width, int height); + public abstract int yOffset(IconEntry entry, int height); + public abstract Dimension finalSize(IconEntry entry, int width, int height); + + public static IconsType detect(int width, int height) { + for (IconsType type : values()) { + if (type.matches(width, height)) { + return type; + } + } + return EMOJI; + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsTypeConfig.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsTypeConfig.java new file mode 100644 index 0000000..7396d40 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsTypeConfig.java @@ -0,0 +1,52 @@ +package fr.openmc.riftengine.core.converter.writers.glyph.icons; + +import fr.openmc.core.bootstrap.integration.OMCLogger; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public class IconsTypeConfig { + + private final Map overrides; + + private IconsTypeConfig(Map overrides) { + this.overrides = overrides; + } + + public static IconsTypeConfig load(Path configPath) throws IOException { + Map overrides = new HashMap<>(); + + if (!Files.exists(configPath)) return new IconsTypeConfig(overrides); + + Yaml yaml = new Yaml(); + try (InputStream in = Files.newInputStream(configPath)) { + Object parsed = yaml.load(in); + if (!(parsed instanceof Map root)) return new IconsTypeConfig(overrides); + + Object section = root.get("icon-type-overrides"); + if (!(section instanceof Map overridesMap)) return new IconsTypeConfig(overrides); + + for (Map.Entry entry : overridesMap.entrySet()) { + String namespacedId = String.valueOf(entry.getKey()); + String typeName = String.valueOf(entry.getValue()); + + try { + overrides.put(namespacedId, IconsType.valueOf(typeName.trim().toUpperCase())); + } catch (IllegalArgumentException e) { + OMCLogger.errorFormatted("Type inconnu pour " + namespacedId + " : " + typeName + " (skip)"); + } + } + } + + return new IconsTypeConfig(overrides); + } + + public IconsType get(String namespacedId) { + return overrides.get(namespacedId); + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsWriter.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsWriter.java new file mode 100644 index 0000000..15389f1 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/IconsWriter.java @@ -0,0 +1,186 @@ +package fr.openmc.riftengine.core.converter.writers.glyph.icons; + +import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.riftengine.core.RiftPlugin; +import fr.openmc.riftengine.core.RiftRegistry; +import fr.openmc.riftengine.core.converter.writers.PackWriter; +import fr.openmc.riftengine.core.registry.glyphs.GlyphsRegistry; +import fr.openmc.riftengine.core.registry.glyphs.types.IconGlyph; +import fr.openmc.riftengine.core.scanner.icons.IconEntry; +import fr.openmc.riftengine.core.scanner.icons.IconScanner; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Writer qui gère la prise en charge des icons/emojis ("font_images" d'items adder) + */ +public class IconsWriter implements PackWriter { + + private final Path itemsAdderContentsPath; + + public IconsWriter(Path itemsAdderContentsPath) { + this.itemsAdderContentsPath = itemsAdderContentsPath; + } + + @Override + public void write(Path bedrockRootPath, Path javaRootPath) throws IOException { + IconsTypeConfig forcedType = IconsTypeConfig.load(RiftPlugin.getInstance().CONFIG_FOLDER + .resolve("icons_types.yml")); + + IconScanner scanner = RiftRegistry.SCANNERS.ICONS; + List entries; + try { + entries = scanner.scan(itemsAdderContentsPath); + } catch (Exception e) { + throw new RuntimeException("Erreur lors du scan des contents d'items adder " + e.getMessage(), e); + } + + List resolved = new ArrayList<>(); + int skipped = 0; + for (IconEntry entry : entries) { + Path imagePath = entry.path().apply(javaRootPath); + + if (!Files.exists(imagePath)) { + OMCLogger.errorFormatted("Image introuvable pour " + entry.namespacedId() + " dans le path " + imagePath + " (skip)"); + skipped++; + continue; + } + + BufferedImage image; + try { + image = ImageIO.read(imagePath.toFile()); + } catch (IOException e) { + OMCLogger.errorFormatted("Erreur de lecture pour " + entry.namespacedId() + " (skip) : " + e.getMessage()); + skipped++; + continue; + } + + if (image == null) { + OMCLogger.errorFormatted("Image non lisible pour " + entry.namespacedId() + " (skip)"); + skipped++; + continue; + } + + IconsType type = forcedType.get(entry.namespacedId()); + if (type == null) { + type = IconsType.detect(image.getWidth(), image.getHeight()); + } + + if (type.equals(IconsType.MENU_GUI)) { + OMCLogger.warnFormatted("Glyph " + entry.namespacedId() + " detécté comme MENU_GUI, il ne sera donc pas en glyph, vu que les menus sont géré autrement"); + continue; + } + + resolved.add(new ResolvedIcon(entry, image, type)); + } + + if (skipped > 0) { + OMCLogger.errorFormatted("Image manquante pour " + skipped + " emoji(s)/icon(s) sur " + entries.size() + "."); + } + + if (resolved.isEmpty()) return; + + Map> groupedByGroupSize = new TreeMap<>(); + for (ResolvedIcon r : resolved) { + Dimension finalSize = r.type().finalSize(r.entry(), r.image().getWidth(), r.image().getHeight()); + int targetFinalSize = Math.max(finalSize.width, finalSize.height); + + Integer group = IconsRegrouperUtils.pickBestSize(targetFinalSize); + groupedByGroupSize.computeIfAbsent(group, _ -> new ArrayList<>()).add(r); + } + + for (Map.Entry> group : groupedByGroupSize.entrySet()) { + Dimension size = new Dimension(group.getKey(), group.getKey()); + List icons = group.getValue(); + + for (int slotPage = 0; slotPage < icons.size(); slotPage += GlyphsRegistry.MAX_PER_PAGE) { + List iconPerPage = icons.subList(slotPage, + Math.min(slotPage + GlyphsRegistry.MAX_PER_PAGE, icons.size())); + writePage(bedrockRootPath, size, iconPerPage); + } + } + } + + private void writePage(Path bedrockRootPath, Dimension size, List icons) throws IOException { + String page = RiftRegistry.GLYPHS.nextGlyphPage(); + + int cellWidth = size.width; + int cellHeight = size.height; + + BufferedImage pageImage = new BufferedImage( + cellWidth * GlyphsRegistry.GRID_SIZE, + cellHeight * GlyphsRegistry.GRID_SIZE, + BufferedImage.TYPE_INT_ARGB + ); + + Graphics2D imageEditable = pageImage.createGraphics(); + imageEditable.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + imageEditable.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + + try { + for (int i = 0; i < icons.size(); i++) { + ResolvedIcon resolvedEmoji = icons.get(i); + int row = i / GlyphsRegistry.GRID_SIZE; + int col = i % GlyphsRegistry.GRID_SIZE; + + IconsType type = resolvedEmoji.type(); + OMCLogger.infoFormatted(resolvedEmoji.entry().namespacedId() + " (" + + resolvedEmoji.image().getWidth() + "x" + resolvedEmoji.image().getHeight() + + ") détecté comme " + type); + + Rectangle placement = centerImage(resolvedEmoji.image(), cellWidth, cellHeight, resolvedEmoji.entry(), type); + imageEditable.drawImage( + resolvedEmoji.image, + col * cellWidth + placement.x, + row * cellHeight + placement.y, + placement.width, + placement.height, + null + ); + + RiftRegistry.GLYPHS.register(new IconGlyph( + resolvedEmoji.entry.namespacedId(), + page, + row, + col, + resolvedEmoji.entry + )); + } + } finally { + imageEditable.dispose(); + } + + String fileName = "glyph_" + page + ".png"; + Path glyphPath = bedrockRootPath.resolve("font").resolve(fileName); + Files.createDirectories(glyphPath.getParent()); + ImageIO.write(pageImage, "png", glyphPath.toFile()); + } + + private Rectangle centerImage(BufferedImage image, int cellWidth, int cellHeight, IconEntry entry, IconsType type) { + Dimension finalSize = type.finalSize(entry, image.getWidth(), image.getHeight()); + int w = Math.min(cellWidth, finalSize.width); + int h = Math.min(cellHeight, finalSize.height); + + int x = (cellWidth - w) / 2; + int y = (cellHeight - h) / 2; + + if (entry.yPosition() != null) { + double yHeight = type.yOffset(entry, h) / (double) h; + y += (int) Math.round(entry.yPosition() * yHeight); + y = Math.max(0, Math.min(y, cellHeight - h)); + } + + return new Rectangle(x, y, w, h); + } + + private record ResolvedIcon(IconEntry entry, BufferedImage image, IconsType type) {} +} diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/SymbolWriter.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/SymbolWriter.java new file mode 100644 index 0000000..6cfa336 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/glyph/icons/SymbolWriter.java @@ -0,0 +1,78 @@ +package fr.openmc.riftengine.core.converter.writers.glyph.icons; + +import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.riftengine.core.RiftPlugin; +import fr.openmc.riftengine.core.RiftRegistry; +import fr.openmc.riftengine.core.converter.writers.PackWriter; +import fr.openmc.riftengine.core.listeners.LoadAfterItemsAdderListener; +import fr.openmc.riftengine.core.registry.glyphs.Glyph; +import fr.openmc.riftengine.core.registry.glyphs.types.IconGlyph; +import fr.openmc.riftengine.core.utils.YmlUtils; +import org.bukkit.Bukkit; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Writer qui prends en charge l'assignation des characteres des glyphs, et qui donne les mappings a items adder + */ +public class SymbolWriter implements PackWriter { + + @Override + public void write(Path bedrockRootPath, Path javaRootPath) throws IOException { + Map> iconByYml = new LinkedHashMap<>(); + + for (Glyph glyph : RiftRegistry.GLYPHS.values()) { + if (!(glyph instanceof IconGlyph iconGlyph)) continue; + iconByYml.computeIfAbsent(iconGlyph.getIcon().sourceYml(), _ -> new ArrayList<>()).add(iconGlyph); + } + + if (iconByYml.isEmpty()) return; + + Yaml yaml = new Yaml(); + + for (Map.Entry> fileEntry : iconByYml.entrySet()) { + Path ymlFile = fileEntry.getKey(); + List icons = fileEntry.getValue(); + + Map root = YmlUtils.loadYml(ymlFile); + + Map fontImagesMap = (Map) root.get("font_images"); + + boolean modified = false; + for (IconGlyph icon : icons) { + Object entryObj = fontImagesMap.get(icon.getIcon().key()); + if (!(entryObj instanceof Map entryMapRaw)) { + OMCLogger.warnFormatted("Clé " + icon.getIcon().key() + " introuvable dans " + ymlFile + "."); + continue; + } + + Map entryMap = (Map) entryMapRaw; + + entryMap.put("symbol", String.valueOf(icon.getBedrockChar())); + modified = true; + } + + if (!modified) continue; + + try (Writer writer = Files.newBufferedWriter(ymlFile)) { + yaml.dump(root, writer); + } + } + + LoadAfterItemsAdderListener.actionToLaunch.add(this::reloadItemsAdder); + } + + private void reloadItemsAdder() { + Bukkit.getScheduler().runTask(RiftPlugin.getInstance(), () -> + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "iazip") + ); + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/listeners/LoadAfterItemsAdderListener.java b/src/main/java/fr/openmc/riftengine/core/listeners/LoadAfterItemsAdderListener.java new file mode 100644 index 0000000..5e762a1 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/listeners/LoadAfterItemsAdderListener.java @@ -0,0 +1,19 @@ +package fr.openmc.riftengine.core.listeners; + +import dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.util.ArrayList; +import java.util.List; + +public class LoadAfterItemsAdderListener implements Listener { + public static List actionToLaunch = new ArrayList<>(); + + @EventHandler + public void onItemsAdderLoadFinish(ItemsAdderLoadDataEvent event) { + actionToLaunch.forEach(Runnable::run); + actionToLaunch.clear(); + } + +} diff --git a/src/main/java/fr/openmc/riftengine/core/registry/glyphs/GlyphsRegistry.java b/src/main/java/fr/openmc/riftengine/core/registry/glyphs/GlyphsRegistry.java index 05a6a07..f6de6d5 100644 --- a/src/main/java/fr/openmc/riftengine/core/registry/glyphs/GlyphsRegistry.java +++ b/src/main/java/fr/openmc/riftengine/core/registry/glyphs/GlyphsRegistry.java @@ -8,8 +8,11 @@ public class GlyphsRegistry extends Registry implements KeyedRegistry { + public static final int GRID_SIZE = 16; + public static final int MAX_PER_PAGE = GRID_SIZE * GRID_SIZE; - private final List SLOT_GLYPH = new ArrayList<>(List.of("E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", + private final List SLOT_GLYPH = new ArrayList<>(List.of( + "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8")); private int nextPageIndex = 0; @@ -20,6 +23,11 @@ public String nextGlyphPage() { return SLOT_GLYPH.get(nextPageIndex++); } + @Override + public void init() { + + } + @Override public String key(Glyph glyph) { return glyph.getNamespacedId(); diff --git a/src/main/java/fr/openmc/riftengine/core/registry/glyphs/types/IconGlyph.java b/src/main/java/fr/openmc/riftengine/core/registry/glyphs/types/IconGlyph.java new file mode 100644 index 0000000..2b37fcf --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/registry/glyphs/types/IconGlyph.java @@ -0,0 +1,15 @@ +package fr.openmc.riftengine.core.registry.glyphs.types; + +import fr.openmc.riftengine.core.registry.glyphs.Glyph; +import fr.openmc.riftengine.core.scanner.icons.IconEntry; +import lombok.Getter; + +@Getter +public class IconGlyph extends Glyph { + private final IconEntry icon; + + public IconGlyph(String namespacedId, String page, int row, int col, IconEntry icon) { + super(namespacedId, page, row, col); + this.icon = icon; + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/riftengine/core/registry/scanner/AbstractScanner.java b/src/main/java/fr/openmc/riftengine/core/registry/scanner/AbstractScanner.java new file mode 100644 index 0000000..dd40f95 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/registry/scanner/AbstractScanner.java @@ -0,0 +1,5 @@ +package fr.openmc.riftengine.core.registry.scanner; + +public abstract class AbstractScanner { + public abstract T scan(P param) throws Exception; +} diff --git a/src/main/java/fr/openmc/riftengine/core/registry/scanner/ScannerRegistry.java b/src/main/java/fr/openmc/riftengine/core/registry/scanner/ScannerRegistry.java new file mode 100644 index 0000000..0622df0 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/registry/scanner/ScannerRegistry.java @@ -0,0 +1,20 @@ +package fr.openmc.riftengine.core.registry.scanner; + +import fr.openmc.core.bootstrap.registries.KeyedRegistry; +import fr.openmc.core.bootstrap.registries.Registry; +import fr.openmc.riftengine.core.scanner.general.YamlNamespaceIAScanner; +import fr.openmc.riftengine.core.scanner.general.YamlScanner; +import fr.openmc.riftengine.core.scanner.icons.IconScanner; + +public class ScannerRegistry extends Registry> + implements KeyedRegistry> { + + public final IconScanner ICONS = register(new IconScanner()); + public final YamlNamespaceIAScanner YAML_ITEMSADDER_NAMESPACE = register(new YamlNamespaceIAScanner()); + public final YamlScanner YAML = register(new YamlScanner()); + + @Override + public String key(AbstractScanner abstractScanner) { + return abstractScanner.getClass().getSimpleName(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/riftengine/core/scanner/general/YamlNamespaceIAScanner.java b/src/main/java/fr/openmc/riftengine/core/scanner/general/YamlNamespaceIAScanner.java new file mode 100644 index 0000000..0474668 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/scanner/general/YamlNamespaceIAScanner.java @@ -0,0 +1,16 @@ +package fr.openmc.riftengine.core.scanner.general; + +import fr.openmc.riftengine.core.registry.scanner.AbstractScanner; + +import java.util.Map; + +public class YamlNamespaceIAScanner extends AbstractScanner> { + @Override + public String scan(Map rootYaml) { + if (rootYaml.get("info") instanceof Map infoMap && infoMap.get("namespace") != null) { + return infoMap.get("namespace").toString(); + } + + return null; + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/scanner/general/YamlScanner.java b/src/main/java/fr/openmc/riftengine/core/scanner/general/YamlScanner.java new file mode 100644 index 0000000..542eacb --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/scanner/general/YamlScanner.java @@ -0,0 +1,26 @@ +package fr.openmc.riftengine.core.scanner.general; + +import fr.openmc.riftengine.core.registry.scanner.AbstractScanner; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.stream.Stream; + +public class YamlScanner extends AbstractScanner, Path> { + @Override + public List scan(Path path) throws Exception { + try (Stream walk = Files.walk(path)) { + return walk + .filter(Files::isRegularFile) + .filter(p -> { + String name = p.getFileName().toString().toLowerCase(Locale.ROOT); + return name.endsWith(".yml") || name.endsWith(".yaml"); + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException("Erreur lors du scan des fichiers yml dans " + path, e);} + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/scanner/icons/IconEntry.java b/src/main/java/fr/openmc/riftengine/core/scanner/icons/IconEntry.java new file mode 100644 index 0000000..c7b7110 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/scanner/icons/IconEntry.java @@ -0,0 +1,47 @@ +package fr.openmc.riftengine.core.scanner.icons; + +import fr.openmc.riftengine.core.utils.IdentifierUtils; +import fr.openmc.riftengine.core.utils.YmlUtils; + +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Function; + +public record IconEntry( + String namespace, + String key, + Function path, + String permission, + boolean showInGui, + Double scaleRatio, + Integer yPosition, + Path sourceYml +) { + public static IconEntry from(Path sourceYml, String namespace, String key, Map data) { + String permission; + + if (data.get("permission") instanceof String perm) { + permission = perm; + } else if (data.get("permission") instanceof Number num) { + permission = "ia.user.image.use." + num.intValue(); + } else { + permission = null; + } + + return new IconEntry( + namespace, + key, + javaRoot -> IdentifierUtils.resolveTextureId(javaRoot, + IdentifierUtils.normalizeId(String.valueOf(data.get("path")), namespace)), + permission, + YmlUtils.getBool(data.get("show_in_gui"), true), + YmlUtils.getDouble(data.get("scale_ratio"), null), + YmlUtils.getInt(data.get("y_position"), null), + sourceYml + ); + } + + public String namespacedId() { + return namespace + ":" + key; + } +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/riftengine/core/scanner/icons/IconScanner.java b/src/main/java/fr/openmc/riftengine/core/scanner/icons/IconScanner.java new file mode 100644 index 0000000..af679fe --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/scanner/icons/IconScanner.java @@ -0,0 +1,42 @@ +package fr.openmc.riftengine.core.scanner.icons; + +import fr.openmc.riftengine.core.RiftRegistry; +import fr.openmc.riftengine.core.registry.scanner.AbstractScanner; +import fr.openmc.riftengine.core.utils.YmlUtils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Scan tout les icons initialisé par ItemsAdder + */ +public class IconScanner extends AbstractScanner, Path> { + public List scan(Path itemsAdderContentsPath) throws Exception { + if (!Files.isDirectory(itemsAdderContentsPath)) return new ArrayList<>(); + + List ymlFiles = RiftRegistry.SCANNERS.YAML.scan(itemsAdderContentsPath); + + List result = new ArrayList<>(); + + for (Path ymlFile : ymlFiles) { + Map root = YmlUtils.loadYml(ymlFile); + + Object fontImagesObj = root.get("font_images"); + if (!(fontImagesObj instanceof Map fontImagesMap)) continue; + + String namespace = RiftRegistry.SCANNERS.YAML_ITEMSADDER_NAMESPACE.scan(root); + + for (Map.Entry entry : fontImagesMap.entrySet()) { + String key = String.valueOf(entry.getKey()); + if (!(entry.getValue() instanceof Map data)) continue; + + result.add(IconEntry.from(ymlFile, namespace, key, data)); + } + } + + return result; + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/utils/IdentifierUtils.java b/src/main/java/fr/openmc/riftengine/core/utils/IdentifierUtils.java index a686ae5..3f3890f 100644 --- a/src/main/java/fr/openmc/riftengine/core/utils/IdentifierUtils.java +++ b/src/main/java/fr/openmc/riftengine/core/utils/IdentifierUtils.java @@ -1,5 +1,7 @@ package fr.openmc.riftengine.core.utils; +import java.nio.file.Path; + public class IdentifierUtils { /** * Normalise un identifiant (qui n'a pas un namespace devant @@ -7,8 +9,47 @@ public class IdentifierUtils { * @return l'id final */ public static String normalizeId(String id) { + return normalizeId(id, "minecraft"); + } + + /** + * Normalise un identifiant (qui n'a pas un namespace devant + * @param id l'id (font/default.png, minecraft:font/default.png) + * @param defaultNamespace le namespace par défaut à utiliser, si on est dans un fichier d'items adder par ex + * @return l'id final + */ + public static String normalizeId(String id, String defaultNamespace) { if (id.split(":").length == 2) return id; - return "minecraft:" + id; + return defaultNamespace + ":" + id; + } + + /** + * Résout un identifiant en un chemin de fichier dans les assets + */ + public static Path resolveTextureId(Path rootPath, String id) { + String normalizedId = normalizeId(id); + String[] split = normalizedId.split(":", 2); + + String namespace; + String texturePath; + + if (split.length == 2) { + namespace = split[0]; + texturePath = split[1]; + } else { + namespace = "minecraft"; + texturePath = split[0]; + } + + if (!texturePath.endsWith(".png")) { + texturePath += ".png"; + } + + return rootPath + .resolve("assets") + .resolve(namespace) + .resolve("textures") + .resolve(texturePath); } } diff --git a/src/main/java/fr/openmc/riftengine/core/utils/YmlUtils.java b/src/main/java/fr/openmc/riftengine/core/utils/YmlUtils.java new file mode 100644 index 0000000..7e3f1f5 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/utils/YmlUtils.java @@ -0,0 +1,37 @@ +package fr.openmc.riftengine.core.utils; + +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public class YmlUtils { + public static Map loadYml(Path path) throws IOException { + try (InputStream in = Files.newInputStream(path)) { + return new Yaml().load(in); + } catch (Exception e) { + throw new IOException("Erreur lors de la lecture du fichier yml " + path, e); + } + } + + public static Integer getInt(Object obj, Integer def) { + if (obj == null) return def; + if (obj instanceof Number n) return n.intValue(); + return Integer.parseInt(obj.toString()); + } + + public static Boolean getBool(Object obj, Boolean def) { + if (obj == null) return def; + if (obj instanceof Boolean b) return b; + return Boolean.parseBoolean(obj.toString()); + } + + public static Double getDouble(Object obj, Double def) { + if (obj == null) return def; + if (obj instanceof Number n) return n.doubleValue(); + return Double.parseDouble(obj.toString()); + } +}