diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5c6e869e0..90d88a6f6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,14 +1,17 @@ -## Pour que votre Pull Request soit accepté, il vous faut : -* Votre code suit-il le [Code de Conduite](https://github.com/ServerOpenMC/PluginV2/blob/master/CODE_OF_CONDUCT.md) ? : -* Avez-vous supprimé au maximum les imports non utilisés ? : -* Avez-vous commentez vos méthodes pour une meilleure lisibilité ? : -* Fournissez un Profileur (/spark profiler) lorsque vous éxécuter vos commandes, méthodes : - -* Les Issues corrigé(e)s/en commun : +## Petit résumé de la PR: ## Étape nécessaire afin que la PR soit fini (si PR en draft) - + + +- [ ] Suivre le [Code de Conduite](https://github.com/ServerOpenMC/PluginV2/blob/master/CODE_OF_CONDUCT.md) +- [ ] Enlever tous les imports non utilisés +- [ ] Bien documenter la feature +- [ ] Fournir un profileur (si besoin/demandé par un admin) +- [ ] Avoir une milestone associée à la PR +- [ ] Valider tout les checks + +* Les Issues corrigée(s) en commun : ## Decrivez vos changements diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..5ede6a2e6 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,14 @@ +changelog: + categories: + - title: Nouvelles Fonctionnalités + labels: + - "🆙 Amélioration" + - ":arrows_counterclockwise: Changement" + - ":package: Features" + - title: Bugs et Optimisations + labels: + - "⚡Optimisation" + - ":sparkles: Fixes" + - title: Autre + labels: + - "*" diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 000000000..1ba3fd9b9 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,75 @@ +name: Build & Test + +on: + pull_request: + paths-ignore: + - '.github/**' + - '.gitignore' + - 'LICENSE' + - 'README.md' + push: + paths-ignore: + - '.github/**' + - '.gitignore' + - 'LICENSE' + - 'README.md' + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Run Tests with Gradle + run: ./gradlew test + + - name: Report test results + uses: dorny/test-reporter@v2 + if: always() + with: + name: Gradle Tests + path: build/test-results/test/*.xml + reporter: java-junit + fail-on-error: true + + build: + name: Build + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Build with snapshot version + env: + SNAPSHOT: true + GITHUB_SHA: ${{ github.sha }} + run: ./gradlew jar + + - name: Upload Plugin jar + uses: actions/upload-artifact@v4 + with: + name: OpenMC-${{ github.sha }} + path: builds/ diff --git a/.github/workflows/check-milestone.yml b/.github/workflows/check-milestone.yml new file mode 100644 index 000000000..da6375eb8 --- /dev/null +++ b/.github/workflows/check-milestone.yml @@ -0,0 +1,17 @@ +name: Milestone Check + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +jobs: + check-milestone: + name: Milestone check + runs-on: ubuntu-latest + steps: + - name: Verify that PR has been assigned a milestone + run: | + if [ "${{ github.event.pull_request.milestone }}" == "" ]; then + echo "❌ PR has not been assigned a milestone." + exit 1 + fi diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index ea33a588c..000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Build DEV Jars - -on: - pull_request: - paths-ignore: - - '.github/PULL_REQUEST_TEMPLATE.md' - - '.gitignore' - - 'LICENSE' - - 'CODE_OF_CONDUCT.md' - - 'CONTRIBUTING.md' - - 'README.md' - push: - paths-ignore: - - '.github/PULL_REQUEST_TEMPLATE.md' - - '.gitignore' - - 'LICENSE' - - 'CODE_OF_CONDUCT.md' - - 'CONTRIBUTING.md' - - 'README.md' - -jobs: - build_dev_jars: - name: Build DEV jars - runs-on: ubuntu-latest - permissions: write-all - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Setup JDK - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '21' - - name: Build with Gradle - run: | - chmod 777 gradlew - ./gradlew shadowJar - git_hash=$(git rev-parse --short "$GITHUB_SHA") - echo "git_hash=$git_hash" >> $GITHUB_ENV - echo "snapshotVersion=5.5-SNAPSHOT" >> $GITHUB_ENV - echo "artifactPath=$(pwd)/builds" >> $GITHUB_ENV - - name: Upload Plugin jar - continue-on-error: true - uses: actions/upload-artifact@v4 - with: - name: OpenMC-${{ env.git_hash }}.jar - path: ${{ env.artifactPath }}/OpenMC.jar diff --git a/.github/workflows/milestone-close.yml b/.github/workflows/milestone-close.yml new file mode 100644 index 000000000..f99ef57b6 --- /dev/null +++ b/.github/workflows/milestone-close.yml @@ -0,0 +1,70 @@ +name: Handle closing a milestone + +on: + milestone: + types: [closed] + +jobs: + build-release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Verify Milestone Name + run: | + if [[ ! "${{ github.event.milestone.title }}" =~ ^[a-zA-Z0-9_\.\-]+$ ]]; then + echo "Name '${{ github.event.milestone.title }}' is not valid" + echo "It should only contain letters (a-z & A-Z), numbers (0-9), periods (.), dashes (-) and underscores (_)" + exit 1 + fi + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Make gradlew executable + run: chmod +x ./gradlew + - name: Build with Gradle + run: | + VERSION=$(echo ${{ github.event.milestone.title }} | tr "." "_") + ./gradlew shadowJar + cp ./builds/OpenMC.jar ./builds/OpenMC-$VERSION.jar + env: + TAG: ${{ github.event.milestone.title }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: OpenMC-${{ github.event.milestone.title }} + path: builds/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.milestone.title }} + name: Release milestone ${{ github.event.milestone.title }} + generate_release_notes: true + fail_on_unmatched_files: true + make_latest: true + body: | + # Mise à jour ${{ github.event.milestone.title }} + + ${{ github.event.milestone.description }} + + Cette mise à jour à été créée automatiquement a la fermeture de la milsetone "${{ github.event.milestone.title }}". + files: builds/** + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + + - name: Deploy to server + uses: ServerOpenMC/pterodactyl-upload-action@v2.5 + with: + panel-host: ${{ secrets.PANEL_HOST }} + api-key: ${{ secrets.API_KEY }} + server-id: ${{ secrets.SERVER_ID }} + source: builds/OpenMC.jar + target: "./plugins/OpenMC.jar" + command: omcrestart diff --git a/.github/workflows/release-to-discord.yml b/.github/workflows/release-to-discord.yml new file mode 100644 index 000000000..d2f82f1ac --- /dev/null +++ b/.github/workflows/release-to-discord.yml @@ -0,0 +1,25 @@ +name: Release to Discord +on: + release: + types: [published] + +jobs: + github-releases-to-discord: + runs-on: ubuntu-latest + name: Release to Discord + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Github Releases To Discord + uses: SethCohen/github-releases-to-discord@v1.16.2 + with: + webhook_url: ${{ secrets.WEBHOOK_URL }} + color: "16021398" # decimal of #f47796 + username: "Mise à jour de OpenMC" + avatar_url: "https://raw.githubusercontent.com/ServerOpenMC/.github/refs/heads/master/brandkit/logo.png" + #content: "||<@&1258420105209974804>||" + footer_title: "Changements" + footer_icon_url: "https://raw.githubusercontent.com/ServerOpenMC/.github/refs/heads/master/brandkit/logo.png" + footer_timestamp: true + max_description: '16384' + reduce_headings: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 5ac9a6ac0..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Gradle tests - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up JDK 21 - uses: actions/setup-java@v3 - with: - java-version: '21' - distribution: 'temurin' - - - name: Run Tests with Gradle - run: ./gradlew test - - - name: Report test results - uses: dorny/test-reporter@v2 - if: always() - with: - name: Gradle Tests - path: build/test-results/test/*.xml - reporter: java-junit - fail-on-error: true \ No newline at end of file diff --git a/build.gradle b/build.gradle index c45f5252f..2b3c7322f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,90 +4,113 @@ plugins { id "io.papermc.paperweight.userdev" version "2.0.0-beta.16" } +def getLatestGitTag() { + try { + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags', '--abbrev=0' + standardOutput = stdout + } + return stdout.toString().trim() + } catch (Exception e) { + return "unknwon" // Default value if no tags exist + } +} + +// Versioning +def tag = System.getenv("TAG") ?: getLatestGitTag() +def isSnapshot = System.getenv('SNAPSHOT')?.toBoolean() ?: false +def gitHash = System.getenv('GITHUB_SHA') ?: 'local' + +version = isSnapshot ? "$tag-SNAPSHOT-${gitHash.take(7)}" : (System.getenv("TAG") ? tag : "dev-$tag") group = 'fr.openmc' -version = "GIT-"+System.getenv("GITHUB_SHA") ?: "unknown" repositories { mavenCentral() - maven {url = "https://oss.sonatype.org/content/groups/public/"} - maven {url = "https://mvn.mathiasd.fr/releases"} - maven {url = "https://maven.enginehub.org/repo/" } - maven {url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/'} - maven {url = "https://repo.dmulloy2.net/repository/public/"} - maven {url = "https://jitpack.io"} // Laissez en dernier + maven { + url "https://oss.sonatype.org/content/groups/public/" + } + maven { + url "https://mvn.mathiasd.fr/releases" + } + maven { + url "https://maven.enginehub.org/repo/" + } + maven { + url "https://repo.extendedclip.com/content/repositories/placeholderapi/" + } + maven { + url "https://repo.dmulloy2.net/repository/public/" + } + maven { + url "https://jitpack.io" // Toujours à la fin + } } dependencies { - paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT") - compileOnly("com.github.LoneDev6:API-ItemsAdder:3.6.3-beta-14") - compileOnly("net.luckperms:api:5.4") - compileOnly("me.clip:placeholderapi:2.11.6") - compileOnly('com.sk89q.worldguard:worldguard-bukkit:7.0.9') - compileOnly("com.github.dmulloy2:ProtocolLib:-SNAPSHOT") // ProtocolLib - - implementation("de.rapha149.signgui:signgui:2.5.0") - implementation("dev.xernas:menulib:1.1.0") - implementation("org.jetbrains:annotations:24.1.0") - implementation("com.github.Revxrsal.Lamp:common:3.2.1") - implementation("com.github.Revxrsal.Lamp:bukkit:3.2.1") - implementation("net.raidstone:wgevents:1.18.1") // Version custom de Webbeh/WorldGuard-Events - - // lombok - compileOnly("org.projectlombok:lombok:1.18.34") - annotationProcessor("org.projectlombok:lombok:1.18.34") - testCompileOnly("org.projectlombok:lombok:1.18.34") - testAnnotationProcessor("org.projectlombok:lombok:1.18.34") - - // unit tests - testImplementation("org.slf4j:slf4j-simple:2.0.16") - testImplementation("org.junit.jupiter:junit-jupiter:5.11.0") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") - testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.45.0") - testImplementation("com.h2database:h2:2.3.232") + paperweight.paperDevBundle '1.21.4-R0.1-SNAPSHOT' + compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.6.3-beta-14' + compileOnly 'net.luckperms:api:5.4' + compileOnly 'me.clip:placeholderapi:2.11.6' + compileOnly 'com.sk89q.worldguard:worldguard-bukkit:7.0.9' + compileOnly 'com.github.dmulloy2:ProtocolLib:-SNAPSHOT' + + implementation 'de.rapha149.signgui:signgui:2.5.0' + implementation 'dev.xernas:menulib:1.1.0' + implementation 'org.jetbrains:annotations:24.1.0' + implementation 'com.github.Revxrsal.Lamp:common:3.2.1' + implementation 'com.github.Revxrsal.Lamp:bukkit:3.2.1' + implementation 'net.raidstone:wgevents:1.18.1' + + compileOnly 'org.projectlombok:lombok:1.18.34' + annotationProcessor 'org.projectlombok:lombok:1.18.34' + testCompileOnly 'org.projectlombok:lombok:1.18.34' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.34' + + testImplementation 'org.slf4j:slf4j-simple:2.0.16' + testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.45.0' + testImplementation 'com.h2database:h2:2.3.232' } def targetJavaVersion = 21 java { - def javaVersion = JavaVersion.toVersion(targetJavaVersion) - sourceCompatibility = javaVersion - targetCompatibility = javaVersion - if (JavaVersion.current() < javaVersion) { - toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) - } + sourceCompatibility = targetJavaVersion + targetCompatibility = targetJavaVersion } -tasks.withType(JavaCompile).configureEach { +tasks.withType(JavaCompile) { options.encoding = 'UTF-8' - - if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { - options.release.set(targetJavaVersion) - } + options.release = targetJavaVersion } processResources { - def props = [version: version] - inputs.properties props - filteringCharset 'UTF-8' - filesMatching('paper-plugin.yml') { - expand props + filteringCharset = 'UTF-8' + + def pluginProps = ["version": version] + inputs.properties(pluginProps) + filesMatching('plugin.yml') { + expand(pluginProps) } } -jar { + +tasks.jar { manifest { - attributes("GIT-COMMIT" : System.getenv("GITHUB_SHA") ?: "unknown") + attributes 'GIT-COMMIT': gitHash } - destinationDirectory.set(file("./builds/")) + destinationDirectory.set(file('./builds/')) } -clean { - delete "builds" +tasks.clean { + delete 'builds' } -shadowJar { - destinationDirectory.set(file("./builds/")) - archiveFileName = "OpenMC.jar" +tasks.shadowJar { + destinationDirectory.set(file('./builds/')) + archiveFileName.set('OpenMC.jar') } -test { useJUnitPlatform() } - -apply plugin: "com.github.johnrengelman.shadow" \ No newline at end of file +tasks.test { + useJUnitPlatform() +} diff --git a/src/main/java/fr/openmc/core/CommandsManager.java b/src/main/java/fr/openmc/core/CommandsManager.java index 0db2166a8..cab287f6d 100644 --- a/src/main/java/fr/openmc/core/CommandsManager.java +++ b/src/main/java/fr/openmc/core/CommandsManager.java @@ -44,6 +44,7 @@ private void registerCommands() { new MailboxCommand(OMCPlugin.getInstance()), new FriendCommand(), new QuestCommand(), + new Restart(), new AdminShopCommand() ); } @@ -51,4 +52,4 @@ private void registerCommands() { private void registerSuggestions() { FriendManager.getInstance().initCommandSuggestion(); } -} \ No newline at end of file +} diff --git a/src/main/java/fr/openmc/core/commands/utils/Restart.java b/src/main/java/fr/openmc/core/commands/utils/Restart.java new file mode 100644 index 000000000..8b4ec035b --- /dev/null +++ b/src/main/java/fr/openmc/core/commands/utils/Restart.java @@ -0,0 +1,66 @@ +package fr.openmc.core.commands.utils; + +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import fr.openmc.core.OMCPlugin; +import fr.openmc.core.utils.messages.MessageType; +import fr.openmc.core.utils.messages.MessagesManager; +import fr.openmc.core.utils.messages.Prefix; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.title.Title; +import revxrsal.commands.annotation.Command; +import revxrsal.commands.annotation.Description; +import revxrsal.commands.bukkit.annotation.CommandPermission; + +public class Restart { + + private static List annouce = List.of(60, 30, 15, 10, 5, 4, 3, 2, 1); + + @Command("omcrestart") + @Description("Redémarre le serveur après 1min") + @CommandPermission("omc.admin.commands.restart") + public void restart(CommandSender sender) { + if (sender instanceof Player) { + MessagesManager.sendMessage(sender, MessagesManager.Message.NOPERMISSION.getMessage(), Prefix.OPENMC, MessageType.ERROR, false); + return; + } + + OMCPlugin plugin = OMCPlugin.getInstance(); + BukkitRunnable update = new BukkitRunnable() { + public int remainingTime = 60; + + @Override + public void run() { + if (remainingTime == 0) + Bukkit.getServer().restart(); + + if (!annouce.contains(remainingTime)) { + remainingTime -= 1; + return; + } + + Component broadcast = Component.text("§7(" + MessageType.WARNING.getPrefix() + "§7) ") + .append(MiniMessage.miniMessage().deserialize(Prefix.OPENMC.getPrefix())) + .append(Component.text(" §7» ") + .append(Component.text("Redémarrage du serveur dans " + remainingTime + " seconde" + (remainingTime == 1 ? "" : "s")))); + + Bukkit.broadcast(broadcast); + + for (Player player : Bukkit.getOnlinePlayers()) { + Title title = Title.title(Component.text("Redémarrage"), Component.text(remainingTime + " seconde" + (remainingTime == 1 ? "" : "s"))); + player.showTitle(title); + } + remainingTime -= 1; + } + }; + + update.runTaskTimer(plugin, 20, 20); + } +} diff --git a/src/main/java/fr/openmc/core/features/economy/EconomyManager.java b/src/main/java/fr/openmc/core/features/economy/EconomyManager.java index 98ced2b38..9dc6d4dc7 100644 --- a/src/main/java/fr/openmc/core/features/economy/EconomyManager.java +++ b/src/main/java/fr/openmc/core/features/economy/EconomyManager.java @@ -96,24 +96,24 @@ public String getFormattedNumber(double number) { } public static String getFormattedSimplifiedNumber(double balance) { - if (balance == 0) { - return "0"; - } - - Map.Entry entry = instance.suffixes.floorEntry((long) balance); - if (entry == null) { - return instance.decimalFormat.format(balance); + if (balance == 0) { + return "0"; + } + + Map.Entry entry = instance.suffixes.floorEntry((long) balance); + if (entry == null) { + return instance.decimalFormat.format(balance); + } + + long divideBy = entry.getKey(); + String suffix = entry.getValue(); + + double truncated = balance / divideBy; + String formatted = instance.decimalFormat.format(truncated); + + return formatted + suffix; } - long divideBy = entry.getKey(); - String suffix = entry.getValue(); - - double truncated = balance / divideBy; - String formatted = instance.decimalFormat.format(truncated); - - return formatted + suffix; -} - public static String getEconomyIcon() { if(Bukkit.getPluginManager().getPlugin("ItemsAdder") != null && Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { return "§f" + PlaceholderAPI.setPlaceholders(null, "%img_aywenito%"); diff --git a/src/main/java/fr/openmc/core/listeners/JoinMessageListener.java b/src/main/java/fr/openmc/core/listeners/JoinMessageListener.java index 3811665c3..a990e76ce 100644 --- a/src/main/java/fr/openmc/core/listeners/JoinMessageListener.java +++ b/src/main/java/fr/openmc/core/listeners/JoinMessageListener.java @@ -10,6 +10,8 @@ import fr.openmc.core.utils.messages.MessagesManager; import fr.openmc.core.utils.messages.Prefix; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; + import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -25,6 +27,22 @@ public class JoinMessageListener implements Listener { public void onPlayerJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); + String version = OMCPlugin.getInstance().getDescription().getVersion(); + String milestoneUrl = "https://github.com/ServerOpenMC/PluginV2/releases/"; + + if (version.matches("^\\d+\\.\\d+\\.\\d+$")) { + milestoneUrl += "tag/" + version; + } else { + milestoneUrl += "latest"; + } + + MessagesManager.sendMessage(player, Component.text("Bienvenu sur OpenMC !"), Prefix.OPENMC, MessageType.INFO, false); + MessagesManager.sendMessage(player, Component.text("Vous jouez actuellement sur la version ") + .append(Component.text(version).clickEvent(ClickEvent.openUrl(milestoneUrl))) + .append(Component.text(" du plugin §aOpenMC§r.")) + .append(Component.text(" Cliquez ici pour voir les changements.").clickEvent(ClickEvent.openUrl(milestoneUrl))) + , Prefix.OPENMC, MessageType.INFO, false); + TabList.getInstance().updateTabList(player); FriendManager.getInstance().getFriendsAsync(player.getUniqueId()).thenAccept(friendsUUIDS -> { @@ -74,4 +92,4 @@ public void onPlayerQuit(PlayerQuitEvent event) { event.quitMessage(Component.text("§8[§c§l-§8] §r" + "§r" + LuckPermsAPI.getFormattedPAPIPrefix(player) + player.getName())); } -} \ No newline at end of file +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f97493753..88896e9c7 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: OpenMC -version: '2.0.0' +version: ${version} main: fr.openmc.core.OMCPlugin api-version: '1.21.4' diff --git a/src/test/resources/plugin.yml b/src/test/resources/plugin.yml index aa03d073b..3ec76e790 100644 --- a/src/test/resources/plugin.yml +++ b/src/test/resources/plugin.yml @@ -1,5 +1,5 @@ name: OpenMC -version: '2.0.0' +version: ${version} main: fr.openmc.core.TestPlugin api-version: '1.21.4' -prefix: OMC \ No newline at end of file +prefix: OMC