diff --git a/.gitignore b/.gitignore index cb9870f..33891e0 100644 --- a/.gitignore +++ b/.gitignore @@ -29,8 +29,7 @@ atlassian-ide-plugin.xml # BlueJ files *.ctxt -# Package Files # -*.jar +# Package Files *.war *.nar *.ear @@ -133,4 +132,5 @@ spigot.yml .factorypath .project .settings/ -bin/ \ No newline at end of file +bin/ +!libs/ \ No newline at end of file diff --git a/build.gradle b/build.gradle index bef3008..4722aec 100644 --- a/build.gradle +++ b/build.gradle @@ -8,13 +8,32 @@ version = '0.1-SNAPSHOT' repositories { mavenCentral() + // * ItemsAdder + maven {url "https://maven.devs.beer/"} + // * GeyserMC + maven {url "https://repo.opencollab.dev/main/"} } dependencies { paperweight.paperDevBundle "${minecraftVersion}.build.${paperVersion}" + // Dependancies + compileOnly "beer.devs:itemsadder-api:${itemsAdderVersion}" + compileOnly "org.geysermc.geyser:api:${geyserApiVersion}" + compileOnly files("libs/OpenMC.jar") implementation "org.jetbrains:annotations:${jetbrainsAnnotationsVersion}" compileOnly "org.projectlombok:lombok:${lombokVersion}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" +} + +processResources { + filteringCharset = 'UTF-8' + + def pluginProps = ["version": version, "minecraftVersion": minecraftVersion] + inputs.properties(pluginProps) + + filesMatching('paper-plugin.yml') { + expand(pluginProps) + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index cb7a812..e00b769 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,5 +6,8 @@ minecraftVersion = 26.2 paperVersion = 118-stable # Dependencies versions +itemsAdderVersion = 4.0.18-beta-10 +geyserApiVersion = 2.10.0-SNAPSHOT + jetbrainsAnnotationsVersion = 26.1.0 lombokVersion = 1.18.46 diff --git a/libs/OpenMC.jar b/libs/OpenMC.jar new file mode 100644 index 0000000..54348ff Binary files /dev/null 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 9e7d71a..12159c1 100644 --- a/src/main/java/fr/openmc/riftengine/core/RiftPlugin.java +++ b/src/main/java/fr/openmc/riftengine/core/RiftPlugin.java @@ -1,17 +1,77 @@ package fr.openmc.riftengine.core; import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.riftengine.core.converter.ConverterManager; +import lombok.Getter; +import lombok.Setter; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.event.bedrock.SessionLoadResourcePacksEvent; +import org.geysermc.geyser.api.pack.PackCodec; +import org.geysermc.geyser.api.pack.ResourcePack; +import org.geysermc.geyser.api.pack.option.PriorityOption; -public class RiftPlugin extends JavaPlugin { +import java.nio.file.Path; +import java.util.function.Consumer; + +public class RiftPlugin extends JavaPlugin implements EventRegistrar { + @Getter + private static RiftPlugin instance; + + @Getter + @Setter + private static ResourcePack resourcePack; + + private ConverterManager converterManager; @Override public void onEnable() { + instance = this; + + // * Registries internal + RiftRegistry.initAll(); + + // * Managers + converterManager = new ConverterManager(this); + updateBedrockResourcePack(); + + // * Listeners + registerEvent(SessionLoadResourcePacksEvent.class, this::onLoadResourcePacks); + OMCLogger.info("RiftEngine activé!"); } @Override public void onDisable() { + RiftRegistry.stopAll(); + OMCLogger.info("RiftEngine désactivé!"); } + + public void updateBedrockResourcePack() { + try { + Path generatedBedrockPack = converterManager.generateConvertedPack(); + resourcePack = ResourcePack.builder(PackCodec.path(generatedBedrockPack)).build(); + } catch (Exception e) { + throw new RuntimeException("Erreur lors de la génération du resourcepack bedrock", e); + } + } + + public void onLoadResourcePacks(SessionLoadResourcePacksEvent event) { + ResourcePack pack = RiftPlugin.getResourcePack(); + + OMCLogger.infoFormatted("RiftEngine pack = " + pack); + + if (pack == null) return; + + event.register(pack, PriorityOption.HIGHEST); + + OMCLogger.successFormatted("RiftEngine: pack registered !"); + } + + public void registerEvent(Class listenerClass, Consumer handler) { + GeyserApi.api().eventBus().subscribe(this, listenerClass, handler); + } } diff --git a/src/main/java/fr/openmc/riftengine/core/RiftRegistry.java b/src/main/java/fr/openmc/riftengine/core/RiftRegistry.java new file mode 100644 index 0000000..2ae5a2e --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/RiftRegistry.java @@ -0,0 +1,71 @@ +package fr.openmc.riftengine.core; + +import fr.openmc.core.bootstrap.features.types.HasListeners; +import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.core.bootstrap.registries.LifecycleRegistry; +import fr.openmc.core.bootstrap.registries.RegistryContext; +import fr.openmc.core.bootstrap.registries.RegistryLoadingType; +import fr.openmc.core.features.events.contents.dailyevents.DailyEventsRegistry; +import fr.openmc.core.features.events.contents.weeklyevents.WeeklyEventsRegistry; +import fr.openmc.core.registry.ambient.CustomAmbientRegistry; +import fr.openmc.core.registry.enchantments.CustomEnchantmentRegistry; +import fr.openmc.core.registry.items.CustomItemRegistry; +import fr.openmc.core.registry.lootboxes.CustomLootboxRegistry; +import fr.openmc.core.registry.loottable.CustomLootTableRegistry; +import fr.openmc.core.registry.mobs.CustomMobRegistry; +import fr.openmc.riftengine.core.registry.mapping.MappingRegistry; +import io.papermc.paper.plugin.bootstrap.BootstrapContext; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings("UnstableApiUsage") +public final class RiftRegistry { + // * Registre globaux + public static MappingRegistry MAPPINGS; + + private static final List LOADED = new ArrayList<>(); + + private static final List ALL = List.of( + new RegistryContext( + () -> MAPPINGS = new MappingRegistry(), + RegistryLoadingType.RUNTIME) + ); + + private RiftRegistry() {} + + public static void initAll() { + for (RegistryContext ctx : RiftRegistry.ALL) { + if (isNotTyped(ctx, RegistryLoadingType.RUNTIME)) continue; + + LifecycleRegistry r = load(ctx); + + r.init(); + OMCLogger.successFormatted("Registre {} chargé pendant le runtime", r.getClass().getSimpleName()); + } + } + + public static void stopAll() { + for (LifecycleRegistry r : LOADED) { + r.stop(); + OMCLogger.successFormatted("Registre {} stoppé", r.getClass().getSimpleName()); + } + LOADED.clear(); + } + + private static LifecycleRegistry load(RegistryContext ctx) { + LifecycleRegistry registry = ctx.registry().get(); + LOADED.add(registry); + return registry; + } + + private static boolean isNotTyped(RegistryContext ctx, RegistryLoadingType type) { + return Arrays.stream(ctx.loadingTypes()).noneMatch(t -> t == type); + } + + private static boolean isTyped(RegistryContext ctx, RegistryLoadingType type) { + return Arrays.stream(ctx.loadingTypes()).anyMatch(t -> t == type); + } +} \ 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 new file mode 100644 index 0000000..d50a4cb --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/ConverterManager.java @@ -0,0 +1,75 @@ +package fr.openmc.riftengine.core.converter; + +import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.riftengine.core.RiftPlugin; +import fr.openmc.riftengine.core.converter.writers.PackWriter; +import fr.openmc.riftengine.core.converter.writers.manifest.ManifestWriter; +import fr.openmc.riftengine.core.converter.writers.manifest.PackIdentity; +import fr.openmc.riftengine.core.converter.writers.translations.TranslationInjector; +import fr.openmc.riftengine.core.utils.ZipUtils; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class ConverterManager { + + private final RiftPlugin plugin; + + private final List writers = new ArrayList<>(); + private final PackIdentity identity; + + public ConverterManager(RiftPlugin plugin) { + this.plugin = plugin; + try { + identity = PackIdentity.loadOrCreate(plugin); + writers.addAll(List.of( + new ManifestWriter(identity), + new TranslationInjector() + )); + } catch (Exception e) { + throw new RuntimeException("Erreur lors d'initialisation du ConverterManager", e); + } + } + + /** + * Prends un pack java et le convertit en pack bedrock + */ + public Path generateConvertedPack() throws IOException { + Path javaPackPath = getJavaPackPath(RiftPlugin.getInstance()); + + Path outputDir = plugin.getDataFolder().toPath().resolve("output"); + Files.createDirectories(outputDir); + + Path workDir = outputDir.resolve("internal"); + Path javaExtractDir = workDir.resolve("java"); + Path bedrockBuildDir = workDir.resolve("bedrock"); + Files.createDirectories(bedrockBuildDir); + + ZipUtils.unzip(javaPackPath, javaExtractDir); + + for (PackWriter writer : writers) { + writer.write(bedrockBuildDir, javaExtractDir); + } + + Path outputPack = outputDir.resolve("bedrock_pack.mcpack"); + Files.deleteIfExists(outputPack); + ZipUtils.zip(bedrockBuildDir, outputPack); + + OMCLogger.successFormatted("Pack bedrock généré : " + outputPack); + return outputPack; + } + + public static Path getJavaPackPath(JavaPlugin plugin) { + File pluginsDir = plugin.getDataFolder().getParentFile(); // * root/plugins/ + File itemsAdderDir = new File(pluginsDir, "ItemsAdder"); // * root/plugins/ItemsAdder + File outputDir = new File(itemsAdderDir, "output"); // * root/plugins/ItemsAdder/output + File generatedDir = new File(outputDir, "generated.zip"); // * root/plugins/ItemsAdder/output/generated.zip + + return generatedDir.toPath(); + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/PackWriter.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/PackWriter.java new file mode 100644 index 0000000..494a14b --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/PackWriter.java @@ -0,0 +1,8 @@ +package fr.openmc.riftengine.core.converter.writers; + +import java.io.IOException; +import java.nio.file.Path; + +public interface PackWriter { + void write(Path bedrockRootPath, Path javaRootPath) throws IOException; +} diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/manifest/ManifestWriter.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/manifest/ManifestWriter.java new file mode 100644 index 0000000..ed66d25 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/manifest/ManifestWriter.java @@ -0,0 +1,43 @@ +package fr.openmc.riftengine.core.converter.writers.manifest; + +import fr.openmc.riftengine.core.converter.writers.PackWriter; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ManifestWriter implements PackWriter { + + private final PackIdentity identity; + + public ManifestWriter(PackIdentity identity) { + this.identity = identity; + } + + @Override + public void write(Path bedrockRootPath, Path javaRootPath) throws IOException { + String manifest = """ + { + "format_version": 2, + "header": { + "name": "RiftPack", + "description": "Généré par RiftEngine à partir de celui de Java", + "uuid": "%s", + "version": [1, 0, 0], + "min_engine_version": [26, 44, 0] + }, + "modules": [ + { + "type": "resources", + "uuid": "%s", + "version": [1, 0, 0] + } + ] + } + """.formatted(identity.headerUuid(), identity.moduleUuid()); + + Files.writeString(bedrockRootPath.resolve("manifest.json"), manifest, StandardCharsets.UTF_8); + + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/manifest/PackIdentity.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/manifest/PackIdentity.java new file mode 100644 index 0000000..3769f3c --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/manifest/PackIdentity.java @@ -0,0 +1,48 @@ +package fr.openmc.riftengine.core.converter.writers.manifest; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +public class PackIdentity { + + private final UUID headerUuid; + private final UUID moduleUuid; + + private PackIdentity(UUID headerUuid, UUID moduleUuid) { + this.headerUuid = headerUuid; + this.moduleUuid = moduleUuid; + } + + public UUID headerUuid() { + return headerUuid; + } + + public UUID moduleUuid() { + return moduleUuid; + } + + public static PackIdentity loadOrCreate(JavaPlugin plugin) throws IOException { + File file = new File(plugin.getDataFolder(), "pack-identity.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(file); + + boolean changed = false; + if (!config.contains("header-uuid")) { + config.set("header-uuid", UUID.randomUUID().toString()); + changed = true; + } + if (!config.contains("module-uuid")) { + config.set("module-uuid", UUID.randomUUID().toString()); + changed = true; + } + if (changed) config.save(file); + + return new PackIdentity( + UUID.fromString(config.getString("header-uuid")), + UUID.fromString(config.getString("module-uuid")) + ); + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/converter/writers/translations/TranslationInjector.java b/src/main/java/fr/openmc/riftengine/core/converter/writers/translations/TranslationInjector.java new file mode 100644 index 0000000..90fd810 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/converter/writers/translations/TranslationInjector.java @@ -0,0 +1,109 @@ +package fr.openmc.riftengine.core.converter.writers.translations; + +import com.google.gson.*; +import fr.openmc.riftengine.core.converter.writers.PackWriter; +import org.geysermc.geyser.api.GeyserApi; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Pas réelement un pack writer, on mets juste les .json des langs dans plugins/Geyser-Spigot/locales/overrides. + */ +public class TranslationInjector implements PackWriter { + + @Override + public void write(Path bedrockRootPath, Path javaRootPath) throws IOException { + Path localesOverrides = GeyserApi.api() + .configDirectory() + .resolve("locales") + .resolve("overrides"); + + Files.createDirectories(localesOverrides); + + List locales = scanAllLocalesPath(javaRootPath); + + Path mergedDirectory = Files.createTempDirectory("merged-locales"); + List mergedLocales = mergeLocales(locales, mergedDirectory); + + for (Path langFile : mergedLocales) { + Files.copy( + langFile, + localesOverrides.resolve(langFile.getFileName()), + StandardCopyOption.REPLACE_EXISTING + ); + } + } + + private List scanAllLocalesPath(Path javaRootPath) throws IOException { + List localesPath = new ArrayList<>(); + + Path assetsPath = javaRootPath.resolve("assets"); + if (!Files.isDirectory(assetsPath)) return localesPath; + + try (Stream namespaces = Files.list(assetsPath)) { + for (Path namespaceDir : namespaces.filter(Files::isDirectory).toList()) { + Path langDir = namespaceDir.resolve("lang"); + if (!Files.isDirectory(langDir)) continue; + + try (Stream langFiles = Files.list(langDir)) { + localesPath.addAll(langFiles.filter(Files::isRegularFile).toList()); + } + } + } + return localesPath; + } + + private List mergeLocales(List localesPath, Path outputDirectory) throws IOException { + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + + Map mergedLocales = new HashMap<>(); + + for (Path localePath : localesPath) { + String fileName = localePath.getFileName().toString(); + + if (!fileName.endsWith(".json")) continue; + + JsonObject locale; + try (Reader reader = Files.newBufferedReader(localePath)) { + locale = JsonParser.parseReader(reader).getAsJsonObject(); + } + + JsonObject merged = mergedLocales.computeIfAbsent( + fileName, + _ -> new JsonObject() + ); + + for (Map.Entry entry : locale.entrySet()) { + merged.add(entry.getKey(), entry.getValue()); + } + } + + Files.createDirectories(outputDirectory); + + List mergedPaths = new ArrayList<>(); + + for (Map.Entry entry : mergedLocales.entrySet()) { + Path outputPath = outputDirectory.resolve(entry.getKey()); + + try (Writer writer = Files.newBufferedWriter(outputPath)) { + gson.toJson(entry.getValue(), writer); + } + + mergedPaths.add(outputPath); + } + + return mergedPaths; + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/registry/mapping/JsonMapping.java b/src/main/java/fr/openmc/riftengine/core/registry/mapping/JsonMapping.java new file mode 100644 index 0000000..f319b9b --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/registry/mapping/JsonMapping.java @@ -0,0 +1,65 @@ +package fr.openmc.riftengine.core.registry.mapping; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import fr.openmc.core.bootstrap.integration.OMCLogger; +import fr.openmc.riftengine.core.RiftPlugin; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public abstract class JsonMapping implements Mapping { + private final Map entries = new HashMap<>(); + + /** + * Chemin du fichier de mapping JSON, ex: "mappings/langs/mapping_26_2.json". + * @return Chemin relatif en partant de resources + */ + public abstract String getBaseFileName(); + + @Override + public void load() { + entries.clear(); + mergeFrom(getBaseFileName()); + + OMCLogger.infoFormatted(getClass().getSimpleName() + " chargé avec " + entries.size() + " entrées"); + } + + private void mergeFrom(String fileName) { + try (InputStream is = RiftPlugin.getInstance().getResource(fileName)) { + if (is == null) return; + + try (Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); + for (String key : json.keySet()) { + entries.put(key, json.get(key).getAsString()); + } + } + } catch (IOException e) { + OMCLogger.errorFormatted("Erreur de lecture du mapping " + fileName + " : " + e.getMessage()); + } + } + + @Override + public Optional resolve(String javaKey) { + return Optional.ofNullable(entries.get(javaKey)); + } + + @Override + public int size() { + return entries.size(); + } + + @Override + public Map asMap() { + return entries; + } + +} diff --git a/src/main/java/fr/openmc/riftengine/core/registry/mapping/Mapping.java b/src/main/java/fr/openmc/riftengine/core/registry/mapping/Mapping.java new file mode 100644 index 0000000..38690a8 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/registry/mapping/Mapping.java @@ -0,0 +1,15 @@ +package fr.openmc.riftengine.core.registry.mapping; + +import java.util.Map; +import java.util.Optional; + +public interface Mapping { + void load(); + Optional resolve(K javaKey); + int size(); + Map asMap(); + + default Mapping get() { + return this; + } +} diff --git a/src/main/java/fr/openmc/riftengine/core/registry/mapping/MappingRegistry.java b/src/main/java/fr/openmc/riftengine/core/registry/mapping/MappingRegistry.java new file mode 100644 index 0000000..62a810f --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/registry/mapping/MappingRegistry.java @@ -0,0 +1,9 @@ +package fr.openmc.riftengine.core.registry.mapping; + +import fr.openmc.core.bootstrap.registries.Registry; + +public class MappingRegistry extends Registry> { + // todo: regarder si c'est réelement utile, inutilisé car le systeme initial de translation ds le pack et tt ne marche pas et geyser on deja un support pour ça + + // ** REGISTER MAPPING ** +} \ No newline at end of file diff --git a/src/main/java/fr/openmc/riftengine/core/utils/ZipUtils.java b/src/main/java/fr/openmc/riftengine/core/utils/ZipUtils.java new file mode 100644 index 0000000..b12b5b8 --- /dev/null +++ b/src/main/java/fr/openmc/riftengine/core/utils/ZipUtils.java @@ -0,0 +1,59 @@ +package fr.openmc.riftengine.core.utils; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class ZipUtils { + public static void unzip(Path zipPath, Path targetDir) throws IOException { + Files.createDirectories(targetDir); + + try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipPath))) { + ZipEntry entry; + + while ((entry = zis.getNextEntry()) != null) { + Path newPath = targetDir.resolve(entry.getName()); + + if (entry.isDirectory()) { + Files.createDirectories(newPath); + } else { + Files.createDirectories(newPath.getParent()); + Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING); + } + + zis.closeEntry(); + } + } + } + + public static void zip(Path sourceDir, Path zipPath) throws IOException { + Files.createDirectories(zipPath.getParent()); + + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath))) { + Files.walk(sourceDir) + .filter(Files::isRegularFile) + .forEach(path -> { + try { + String entryName = sourceDir + .relativize(path) + .toString() + .replace('\\', '/'); + + ZipEntry entry = new ZipEntry(entryName); + zos.putNextEntry(entry); + Files.copy(path, zos); + zos.closeEntry(); + + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + +} diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 0cdb4ed..70b0652 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -8,8 +8,11 @@ api-version: ${minecraftVersion} dependencies: server: OpenMC: - load: AFTER + load: BEFORE required: true ItemsAdder: - load: AFTER + load: BEFORE + required: true + Geyser-Spigot: + load: BEFORE required: true \ No newline at end of file