diff --git a/api/build.gradle.kts b/api/build.gradle.kts
index bd54a9ce4..eac02ebeb 100644
--- a/api/build.gradle.kts
+++ b/api/build.gradle.kts
@@ -1,8 +1,24 @@
plugins {
+ // Allow blossom to mark sources root of templates
+ idea
id("geyser.publish-conventions")
+ alias(libs.plugins.blossom)
}
dependencies {
api(libs.base.api)
api(libs.math)
-}
\ No newline at end of file
+}
+
+version = property("version")!!
+val apiVersion = (version as String).removeSuffix("-SNAPSHOT")
+
+sourceSets {
+ main {
+ blossom {
+ javaSources {
+ property("version", apiVersion)
+ }
+ }
+ }
+}
diff --git a/api/src/main/java-templates/org.geysermc.geyser.api/BuildData.java b/api/src/main/java-templates/org.geysermc.geyser.api/BuildData.java
new file mode 100644
index 000000000..f9a580e7b
--- /dev/null
+++ b/api/src/main/java-templates/org.geysermc.geyser.api/BuildData.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api;
+
+import org.geysermc.api.util.ApiVersion;
+
+/**
+ * Not a public API. For internal use only. May change without notice.
+ * This class is processed before compilation to insert build properties.
+ */
+class BuildData {
+ static final String VERSION = "{{ version }}";
+ static final ApiVersion API_VERSION;
+
+ static {
+ String[] parts = VERSION.split("\\.");
+ if (parts.length != 3) {
+ throw new RuntimeException("Invalid api version: " + VERSION);
+ }
+
+ try {
+ int human = Integer.parseInt(parts[0]);
+ int major = Integer.parseInt(parts[1]);
+ int minor = Integer.parseInt(parts[2]);
+ API_VERSION = new ApiVersion(human, major, minor);
+ } catch (Exception e) {
+ throw new RuntimeException("Invalid api version: " + VERSION, e);
+ }
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
index a9327d0db..5c20d06e1 100644
--- a/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
+++ b/api/src/main/java/org/geysermc/geyser/api/GeyserApi.java
@@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.Geyser;
import org.geysermc.api.GeyserApiBase;
+import org.geysermc.api.util.ApiVersion;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.EventBus;
@@ -169,4 +170,14 @@ public interface GeyserApi extends GeyserApiBase {
static GeyserApi api() {
return Geyser.api(GeyserApi.class);
}
+
+ /**
+ * Returns the {@link ApiVersion} representing the current Geyser api version.
+ * See the Geyser version outline)
+ *
+ * @return the current geyser api version
+ */
+ default ApiVersion geyserApiVersion() {
+ return BuildData.API_VERSION;
+ }
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
index 2df3ee815..25daf450f 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
@@ -59,33 +59,46 @@ public interface ExtensionDescription {
String main();
/**
- * Gets the extension's major api version
+ * Represents the human api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
*
- * @return the extension's major api version
+ * @return the extension's requested human api version
+ */
+ int humanApiVersion();
+
+ /**
+ * Represents the major api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
+ *
+ * @return the extension's requested major api version
*/
int majorApiVersion();
/**
- * Gets the extension's minor api version
+ * Represents the minor api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
*
- * @return the extension's minor api version
+ * @return the extension's requested minor api version
*/
int minorApiVersion();
/**
- * Gets the extension's patch api version
- *
- * @return the extension's patch api version
+ * No longer in use. Geyser is now using an adaption of the romantic versioning scheme.
+ * See here for details.
*/
- int patchApiVersion();
+ @Deprecated(forRemoval = true)
+ default int patchApiVersion() {
+ return minorApiVersion();
+ }
/**
- * Gets the extension's api version.
- *
- * @return the extension's api version
+ * Returns the extension's requested Geyser Api version.
*/
default String apiVersion() {
- return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion();
+ return humanApiVersion() + "." + majorApiVersion() + "." + minorApiVersion();
}
/**
diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java
index 239ffc450..a84f12813 100644
--- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java
+++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java
@@ -43,9 +43,9 @@ import java.util.regex.Pattern;
public record GeyserExtensionDescription(@NonNull String id,
@NonNull String name,
@NonNull String main,
+ int humanApiVersion,
int majorApiVersion,
int minorApiVersion,
- int patchApiVersion,
@NonNull String version,
@NonNull List authors) implements ExtensionDescription {
@@ -82,9 +82,9 @@ public record GeyserExtensionDescription(@NonNull String id,
throw new InvalidDescriptionException(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion));
}
String[] api = apiVersion.split("\\.");
- int majorApi = Integer.parseUnsignedInt(api[0]);
- int minorApi = Integer.parseUnsignedInt(api[1]);
- int patchApi = Integer.parseUnsignedInt(api[2]);
+ int humanApi = Integer.parseUnsignedInt(api[0]);
+ int majorApi = Integer.parseUnsignedInt(api[1]);
+ int minorApi = Integer.parseUnsignedInt(api[2]);
List authors = new ArrayList<>();
if (source.author != null) {
@@ -94,7 +94,7 @@ public record GeyserExtensionDescription(@NonNull String id,
authors.addAll(source.authors);
}
- return new GeyserExtensionDescription(id, name, main, majorApi, minorApi, patchApi, version, authors);
+ return new GeyserExtensionDescription(id, name, main, humanApi, majorApi, minorApi, version, authors);
}
@NonNull
diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java
index 2f0ff1580..a56e00671 100644
--- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java
+++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java
@@ -29,10 +29,15 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
-import org.geysermc.api.Geyser;
+import org.geysermc.api.util.ApiVersion;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.event.ExtensionEventBus;
-import org.geysermc.geyser.api.extension.*;
+import org.geysermc.geyser.api.extension.Extension;
+import org.geysermc.geyser.api.extension.ExtensionDescription;
+import org.geysermc.geyser.api.extension.ExtensionLoader;
+import org.geysermc.geyser.api.extension.ExtensionLogger;
+import org.geysermc.geyser.api.extension.ExtensionManager;
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
import org.geysermc.geyser.extension.event.GeyserExtensionEventBus;
@@ -40,7 +45,12 @@ import org.geysermc.geyser.text.GeyserLocale;
import java.io.IOException;
import java.io.Reader;
-import java.nio.file.*;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -176,16 +186,22 @@ public class GeyserExtensionLoader extends ExtensionLoader {
return;
}
- // Completely different API version
- if (description.majorApiVersion() != Geyser.api().majorApiVersion()) {
- GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
- return;
- }
+ // Check whether an extensions' requested api version is compatible
+ ApiVersion.Compatibility compatibility = GeyserApi.api().geyserApiVersion().supportsRequestedVersion(
+ description.humanApiVersion(),
+ description.majorApiVersion(),
+ description.minorApiVersion()
+ );
- // If the extension requires new API features, being backwards compatible
- if (description.minorApiVersion() > Geyser.api().minorApiVersion()) {
- GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
- return;
+ if (compatibility != ApiVersion.Compatibility.COMPATIBLE) {
+ // Workaround for the switch to the Geyser API version instead of the Base API version in extensions
+ if (compatibility == ApiVersion.Compatibility.HUMAN_DIFFER && description.humanApiVersion() == 1) {
+ GeyserImpl.getInstance().getLogger().warning("The extension %s requested the Base API version %s, which is deprecated in favor of specifying the Geyser API version. Please update the extension, or contact its developer."
+ .formatted(name, description.apiVersion()));
+ } else {
+ GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
+ return;
+ }
}
GeyserExtensionContainer container = this.loadExtension(path, description);
diff --git a/gradle.properties b/gradle.properties
index a222b1d99..10d236a1b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
group=org.geysermc
id=geyser
-version=2.4.0-SNAPSHOT
+version=2.4.1-SNAPSHOT
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5695f198a..ee32c7950 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-base-api = "1.0.0-SNAPSHOT"
+base-api = "1.0.1-SNAPSHOT"
cumulus = "1.1.2"
erosion = "1.1-20240515.191456-1"
events = "1.1-SNAPSHOT"