From 197f8ac4c493728e460ffd2a4c8dccb009a2eb7f Mon Sep 17 00:00:00 2001 From: MrMicky Date: Sat, 15 Feb 2025 08:21:24 +0100 Subject: [PATCH] Add Fabric 1.21 support --- .github/workflows/build.yml | 9 +- .../azlink/common/platform/PlatformType.java | 1 + fabric/build.gradle | 59 +++++++ .../azlink/fabric/AzLinkFabricMod.java | 152 ++++++++++++++++++ .../fabric/command/FabricCommandExecutor.java | 72 +++++++++ .../fabric/command/FabricCommandSource.java | 40 +++++ .../azlink/fabric/command/FabricPlayer.java | 35 ++++ .../azlink/fabric/command/TextAdapter.java | 27 ++++ fabric/src/main/resources/fabric.mod.json | 27 ++++ settings.gradle | 12 ++ 10 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 fabric/build.gradle create mode 100644 fabric/src/main/java/com/azuriom/azlink/fabric/AzLinkFabricMod.java create mode 100644 fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandExecutor.java create mode 100644 fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandSource.java create mode 100644 fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricPlayer.java create mode 100644 fabric/src/main/java/com/azuriom/azlink/fabric/command/TextAdapter.java create mode 100644 fabric/src/main/resources/fabric.mod.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ae205d..0f32166 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - java-version: [ 17 ] + java-version: [ 21 ] steps: - name: Checkout repository @@ -46,3 +46,10 @@ jobs: name: AzLink-Legacy path: universal-legacy/build/libs/AzLink-Legacy-*.jar overwrite: true + + - name: Upload AzLink-Fabric.jar + uses: actions/upload-artifact@v4 + with: + name: AzLink-Fabric + path: fabric/build/libs/AzLink-Fabric-*.jar + overwrite: true diff --git a/common/src/main/java/com/azuriom/azlink/common/platform/PlatformType.java b/common/src/main/java/com/azuriom/azlink/common/platform/PlatformType.java index b648e2a..401ab5b 100644 --- a/common/src/main/java/com/azuriom/azlink/common/platform/PlatformType.java +++ b/common/src/main/java/com/azuriom/azlink/common/platform/PlatformType.java @@ -6,6 +6,7 @@ public enum PlatformType { BUNGEE("BungeeCord"), SPONGE("Sponge"), VELOCITY("Velocity"), + FABRIC("Fabric"), NUKKIT("Nukkit"); private final String name; diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 0000000..18f7a29 --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'fabric-loom' version '1.2.7' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +ext { + minecraft_version = '1.21.1' + yarn_mappings = '1.21.1+build.3' + loader_version = '0.16.7' + fabric_version = '0.106.0+1.21.1' +} + +dependencies { + implementation project(':azlink-common') + implementation 'net.kyori:adventure-api:4.12.0' + implementation 'net.kyori:adventure-text-serializer-gson:4.14.0' + implementation 'net.kyori:adventure-text-serializer-legacy:4.14.0' + minecraft "com.mojang:minecraft:${project.ext.minecraft_version}" + mappings "net.fabricmc:yarn:${project.ext.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.ext.loader_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.ext.fabric_version}" +} + +loom { + runtimeOnlyLog4j = true +} + +processResources { + filesMatching('**/*.json') { + expand 'version': project.version + } +} + +shadowJar { + dependencies { + exclude 'net.fabricmc:.*' + include dependency('com.azuriom:.*') + include dependency('net.kyori:.*') + } + + relocate 'net.kyori', 'com.azuriom.azlink.libs' +} + +remapJar { + dependsOn tasks.shadowJar + mustRunAfter tasks.shadowJar + inputFile = shadowJar.archiveFile + addNestedDependencies = true + archiveFileName = "AzLink-Fabric-${project.version}-${project.ext.minecraft_version}.jar" +} + +artifacts { + archives remapJar +} diff --git a/fabric/src/main/java/com/azuriom/azlink/fabric/AzLinkFabricMod.java b/fabric/src/main/java/com/azuriom/azlink/fabric/AzLinkFabricMod.java new file mode 100644 index 0000000..771b153 --- /dev/null +++ b/fabric/src/main/java/com/azuriom/azlink/fabric/AzLinkFabricMod.java @@ -0,0 +1,152 @@ +package com.azuriom.azlink.fabric; + +import com.azuriom.azlink.common.AzLinkPlatform; +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.command.CommandSender; +import com.azuriom.azlink.common.data.WorldData; +import com.azuriom.azlink.common.logger.LoggerAdapter; +import com.azuriom.azlink.common.logger.Slf4jLoggerAdapter; +import com.azuriom.azlink.common.platform.PlatformInfo; +import com.azuriom.azlink.common.platform.PlatformType; +import com.azuriom.azlink.common.scheduler.JavaSchedulerAdapter; +import com.azuriom.azlink.common.scheduler.SchedulerAdapter; +import com.azuriom.azlink.common.tasks.TpsTask; +import com.azuriom.azlink.fabric.command.FabricCommandExecutor; +import com.azuriom.azlink.fabric.command.FabricPlayer; +import com.google.common.collect.Iterables; +import com.google.common.collect.Streams; +import net.fabricmc.api.DedicatedServerModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; + +public final class AzLinkFabricMod implements AzLinkPlatform, DedicatedServerModInitializer { + + private static final Logger LOGGER = LoggerFactory.getLogger("azlink"); + + private final LoggerAdapter logger = new Slf4jLoggerAdapter(LOGGER); + private final TpsTask tpsTask = new TpsTask(); + + private final ModContainer modContainer; + private final AzLinkPlugin plugin; + private SchedulerAdapter scheduler; + + public AzLinkFabricMod() { + this.modContainer = FabricLoader.getInstance() + .getModContainer("azlink") + .orElseThrow(() -> new RuntimeException("Unable to get the mod container.")); + this.plugin = new AzLinkPlugin(this); + } + + @Override + public void onInitializeServer() { + var command = new FabricCommandExecutor<>(this.plugin); + + ServerLifecycleEvents.SERVER_STARTING.register(this::onServerStart); + ServerLifecycleEvents.SERVER_STOPPING.register(this::onServerStop); + ServerTickEvents.START_SERVER_TICK.register(s -> this.tpsTask.run()); + + CommandRegistrationCallback.EVENT + .register((dispatcher, registry, env) -> command.register(dispatcher)); + } + + public void onServerStart(MinecraftServer server) { + this.scheduler = this.initScheduler(); + this.plugin.init(); + } + + public void onServerStop(MinecraftServer server) { + if (this.plugin != null) { + this.plugin.shutdown(); + } + } + + @Override + public AzLinkPlugin getPlugin() { + return this.plugin; + } + + @Override + public LoggerAdapter getLoggerAdapter() { + return this.logger; + } + + @Override + public SchedulerAdapter getSchedulerAdapter() { + return this.scheduler; + } + + @Override + public PlatformType getPlatformType() { + return PlatformType.FABRIC; + } + + @Override + public PlatformInfo getPlatformInfo() { + return FabricLoader.getInstance() + .getModContainer("fabric") + .map(ModContainer::getMetadata) + .map(m -> new PlatformInfo(m.getName(), m.getVersion().getFriendlyString())) + .orElse(new PlatformInfo("unknown", "unknown")); + } + + @Override + public String getPluginVersion() { + return this.modContainer.getMetadata().getVersion().getFriendlyString(); + } + + @Override + public Path getDataDirectory() { + return FabricLoader.getInstance().getConfigDir().resolve("AzLink"); + } + + @Override + public Optional getWorldData() { + int loadedChunks = Streams.stream(getServer().getWorlds()) + .mapToInt(w -> w.getChunkManager().getLoadedChunkCount()) + .sum(); + int entities = Streams.stream(getServer().getWorlds()) + .mapToInt(w -> Iterables.size(w.iterateEntities())) + .sum(); + + return Optional.of(new WorldData(this.tpsTask.getTps(), loadedChunks, entities)); + } + + @Override + public Stream getOnlinePlayers() { + return getServer().getPlayerManager() + .getPlayerList() + .stream() + .map(FabricPlayer::new); + } + + @Override + public void dispatchConsoleCommand(String command) { + ServerCommandSource source = getServer().getCommandSource(); + getServer().getCommandManager().executeWithPrefix(source, command); + } + + @Override + public int getMaxPlayers() { + return getServer().getMaxPlayerCount(); + } + + private SchedulerAdapter initScheduler() { + return new JavaSchedulerAdapter(getServer()::executeSync); + } + + @SuppressWarnings("deprecation") + private MinecraftServer getServer() { + return (MinecraftServer) FabricLoader.getInstance().getGameInstance(); + } +} diff --git a/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandExecutor.java b/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandExecutor.java new file mode 100644 index 0000000..567c0ca --- /dev/null +++ b/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandExecutor.java @@ -0,0 +1,72 @@ +package com.azuriom.azlink.fabric.command; + +import com.azuriom.azlink.common.AzLinkPlugin; +import com.azuriom.azlink.common.command.AzLinkCommand; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.concurrent.CompletableFuture; + +public class FabricCommandExecutor + extends AzLinkCommand implements Command, SuggestionProvider { + + public FabricCommandExecutor(AzLinkPlugin plugin) { + super(plugin); + } + + @Override + public int run(CommandContext context) { + try { + String args = getArguments(context); + FabricCommandSource source = new FabricCommandSource(context.getSource()); + + this.execute(source, args.split(" ", -1)); + } catch (Exception e) { + this.plugin.getLogger().error("An error occurred while executing command", e); + } + + return Command.SINGLE_SUCCESS; + } + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { + try { + String args = getArguments(context); + FabricCommandSource source = new FabricCommandSource(context.getSource()); + + this.tabComplete(source, args.split(" ", -1)).forEach(builder::suggest); + } catch (Exception e) { + this.plugin.getLogger().error("An error occurred while getting suggestions", e); + } + + return builder.buildFuture(); + } + + public void register(CommandDispatcher dispatcher) { + LiteralArgumentBuilder command = LiteralArgumentBuilder.literal("azlink") + .then(RequiredArgumentBuilder + .argument("args", StringArgumentType.greedyString()) + .executes(this) + .suggests(this) + ) + .executes(this); + + dispatcher.register(command); + } + + private String getArguments(CommandContext context) { + try { + return context.getArgument("args", String.class); + } catch (IllegalArgumentException e) { + return ""; + } + } +} diff --git a/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandSource.java b/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandSource.java new file mode 100644 index 0000000..9c316b5 --- /dev/null +++ b/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricCommandSource.java @@ -0,0 +1,40 @@ +package com.azuriom.azlink.fabric.command; + +import com.azuriom.azlink.common.command.CommandSender; +import net.minecraft.entity.Entity; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.UUID; + +public class FabricCommandSource implements CommandSender { + + private final ServerCommandSource source; + + public FabricCommandSource(ServerCommandSource source) { + this.source = source; + } + + @Override + public String getName() { + return this.source.getName(); + } + + @Override + public UUID getUuid() { + Entity entity = this.source.getEntity(); + + return entity != null + ? entity.getUuid() + : UUID.nameUUIDFromBytes(this.source.getName().getBytes()); + } + + @Override + public void sendMessage(String message) { + this.source.sendMessage(TextAdapter.toText(message)); + } + + @Override + public boolean hasPermission(String permission) { + return this.source.hasPermissionLevel(3); + } +} diff --git a/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricPlayer.java b/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricPlayer.java new file mode 100644 index 0000000..a92b9df --- /dev/null +++ b/fabric/src/main/java/com/azuriom/azlink/fabric/command/FabricPlayer.java @@ -0,0 +1,35 @@ +package com.azuriom.azlink.fabric.command; + +import com.azuriom.azlink.common.command.CommandSender; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.UUID; + +public class FabricPlayer implements CommandSender { + + private final ServerPlayerEntity player; + + public FabricPlayer(ServerPlayerEntity player) { + this.player = player; + } + + @Override + public String getName() { + return this.player.getName().getString(); + } + + @Override + public UUID getUuid() { + return this.player.getUuid(); + } + + @Override + public void sendMessage(String message) { + this.player.sendMessage(TextAdapter.toText(message)); + } + + @Override + public boolean hasPermission(String permission) { + return this.player.hasPermissionLevel(3); + } +} diff --git a/fabric/src/main/java/com/azuriom/azlink/fabric/command/TextAdapter.java b/fabric/src/main/java/com/azuriom/azlink/fabric/command/TextAdapter.java new file mode 100644 index 0000000..a4b7493 --- /dev/null +++ b/fabric/src/main/java/com/azuriom/azlink/fabric/command/TextAdapter.java @@ -0,0 +1,27 @@ +package com.azuriom.azlink.fabric.command; + +import com.google.gson.JsonElement; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.text.Text; + +public final class TextAdapter { + + private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder() + .character('&') + .extractUrls() + .build(); + + private TextAdapter() { + throw new UnsupportedOperationException(); + } + + public static Text toText(String message) { + TextComponent component = LEGACY_SERIALIZER.deserialize(message); + JsonElement json = GsonComponentSerializer.gson().serializeToTree(component); + + return Text.Serialization.fromJsonTree(json, DynamicRegistryManager.EMPTY); + } +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..15930be --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": 1, + "id": "azlink", + "version": "${version}", + "name": "AzLink", + "description": "The mod to link your Azuriom website with your Fabric server.", + "authors": [ + "Azuriom" + ], + "contact": { + "homepage": "https://azuriom.com/", + "sources": "https://github.com/Azuriom/AzLink" + }, + "license": "MIT", + "environment": "server", + "entrypoints": { + "server": [ + "com.azuriom.azlink.fabric.AzLinkFabricMod" + ] + }, + "mixins": [], + "depends": { + "fabricloader": ">=0.14.11", + "fabric-api": "*", + "minecraft": "~1.21" + } +} diff --git a/settings.gradle b/settings.gradle index 2b4ff28..d1044b4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,14 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + } +} + rootProject.name = 'azlink' def modules = [ @@ -10,6 +21,7 @@ def modules = [ 'nukkit', 'universal', 'universal-legacy', + 'fabric', ] modules.each {