diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6c93842..bc3588aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Setup Gradle uses: gradle/gradle-build-action@v2 diff --git a/.github/workflows/geyser.yml b/.github/workflows/geyser.yml index 695e11d9..afdb6a00 100644 --- a/.github/workflows/geyser.yml +++ b/.github/workflows/geyser.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Setup Gradle uses: gradle/gradle-build-action@v2 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491..e6441136 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e093..b82aa23a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..25da30db 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle b/settings.gradle index 3c0ab7f5..6c3c251a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,10 +8,18 @@ pluginManagement { name = 'Forge' url = 'https://maven.minecraftforge.net/' } + maven { + name = 'NeoForge' + url = 'https://maven.neoforged.net/releases' + } gradlePluginPortal() } } +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} + rootProject.name = 'spark' include ( 'spark-api', @@ -23,6 +31,7 @@ include ( 'spark-sponge7', 'spark-sponge8', 'spark-forge', + 'spark-neoforge', 'spark-fabric', 'spark-nukkit', 'spark-waterdog', diff --git a/spark-fabric/build.gradle b/spark-fabric/build.gradle index baf4ea9b..5ab88ee0 100644 --- a/spark-fabric/build.gradle +++ b/spark-fabric/build.gradle @@ -1,13 +1,13 @@ import net.fabricmc.loom.task.RemapJarTask plugins { - id 'fabric-loom' version '1.4-SNAPSHOT' + id 'fabric-loom' version '1.6-SNAPSHOT' id 'com.github.johnrengelman.shadow' version '8.1.1' } tasks.withType(JavaCompile) { - // override, compile targeting J17 - options.release = 17 + // override, compile targeting J21 + options.release = 21 } repositories { @@ -28,9 +28,9 @@ configurations { dependencies { // https://modmuss50.me/fabric.html - minecraft 'com.mojang:minecraft:1.20.4' - mappings 'net.fabricmc:yarn:1.20.4+build.1:v2' - modImplementation 'net.fabricmc:fabric-loader:0.15.1' + minecraft 'com.mojang:minecraft:1.20.5' + mappings 'net.fabricmc:yarn:1.20.5+build.1:v2' + modImplementation 'net.fabricmc:fabric-loader:0.15.10' Set apiModules = [ "fabric-api-base", @@ -40,12 +40,12 @@ dependencies { // Add each module as a dependency apiModules.forEach { - modImplementation(fabricApi.module(it, '0.91.2+1.20.4')) + modImplementation(fabricApi.module(it, '0.97.5+1.20.5')) } - include(modImplementation('me.lucko:fabric-permissions-api:0.2-SNAPSHOT')) + include(modImplementation('me.lucko:fabric-permissions-api:0.3.1')) - modImplementation('eu.pb4:placeholder-api:2.0.0-beta.4+1.19') + modImplementation('eu.pb4:placeholder-api:2.4.0-pre.1+1.20.5') shade project(':spark-common') } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricCommandSender.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricCommandSender.java index ca7b8939..7da89966 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricCommandSender.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricCommandSender.java @@ -26,6 +26,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.CommandOutput; import net.minecraft.server.rcon.RconCommandOutput; @@ -64,7 +65,7 @@ public UUID getUniqueId() { @Override public void sendMessage(Component message) { - Text component = Text.Serialization.fromJsonTree(GsonComponentSerializer.gson().serializeToTree(message)); + Text component = Text.Serialization.fromJsonTree(GsonComponentSerializer.gson().serializeToTree(message), DynamicRegistryManager.EMPTY); super.delegate.sendMessage(component); } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java index 22794c27..91797bb4 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java @@ -54,7 +54,7 @@ private JsonElement datapackMetadata() { obj.addProperty("name", profile.getDisplayName().getString()); obj.addProperty("description", profile.getDescription().getString()); obj.addProperty("source", resourcePackSource(profile.getSource())); - datapacks.add(profile.getName(), obj); + datapacks.add(profile.getId(), obj); } return datapacks; } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java index dc95bf41..dc4321da 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java @@ -30,6 +30,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.text.Text; import net.minecraft.util.Identifier; @@ -60,7 +61,7 @@ private static PlaceholderResult toResult(Component component) { } private static Text toText(Component component) { - return Text.Serialization.fromJsonTree(GsonComponentSerializer.gson().serializeToTree(component)); + return Text.Serialization.fromJsonTree(GsonComponentSerializer.gson().serializeToTree(component), DynamicRegistryManager.EMPTY); } } diff --git a/spark-fabric/src/main/resources/fabric.mod.json b/spark-fabric/src/main/resources/fabric.mod.json index f1f0ad4e..a6e0a742 100644 --- a/spark-fabric/src/main/resources/fabric.mod.json +++ b/spark-fabric/src/main/resources/fabric.mod.json @@ -30,6 +30,7 @@ "fabricloader": ">=0.4.0", "fabric-api-base": "*", "fabric-command-api-v2": "*", - "fabric-lifecycle-events-v1" : "*" + "fabric-lifecycle-events-v1" : "*", + "fabric-permissions-api-v0": "*" } } diff --git a/spark-fabric/src/main/resources/spark.mixins.json b/spark-fabric/src/main/resources/spark.mixins.json index beaca2f5..27676a64 100644 --- a/spark-fabric/src/main/resources/spark.mixins.json +++ b/spark-fabric/src/main/resources/spark.mixins.json @@ -1,7 +1,6 @@ { "required": true, "package": "me.lucko.spark.fabric.mixin", - "compatibilityLevel": "JAVA_17", "client": [ "ClientEntityManagerAccessor", "ClientWorldAccessor", diff --git a/spark-geyser/src/main/java/me/lucko/spark/geyser/GeyserSparkExtension.java b/spark-geyser/src/main/java/me/lucko/spark/geyser/GeyserSparkExtension.java index 0d07c224..84e87e61 100644 --- a/spark-geyser/src/main/java/me/lucko/spark/geyser/GeyserSparkExtension.java +++ b/spark-geyser/src/main/java/me/lucko/spark/geyser/GeyserSparkExtension.java @@ -53,11 +53,11 @@ public void onPreInitialize(GeyserPreInitializeEvent event) { this.logger().severe("spark is only supported on standalone Geyser instances! If you wish to use it on other platforms please download the spark version for that platform."); this.disable(); } + this.platform = new SparkPlatform(this); } @Subscribe public void onPostInitialize(GeyserPostInitializeEvent event) { - this.platform = new SparkPlatform(this); this.platform.enable(); } diff --git a/spark-neoforge/build.gradle b/spark-neoforge/build.gradle new file mode 100644 index 00000000..177449fc --- /dev/null +++ b/spark-neoforge/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'net.neoforged.gradle.userdev' version '7.0.97' +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(17) + +tasks.withType(JavaCompile) { + // override, compile targeting J17 + options.release = 17 +} + +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} + +configurations { + shade + implementation.extendsFrom shade +} + +dependencies { + implementation "net.neoforged:neoforge:20.4.223" + shade project(':spark-common') +} + +processResources { + from(sourceSets.main.resources.srcDirs) { + include 'META-INF/mods.toml' + expand ( + 'pluginVersion': project.pluginVersion, + 'pluginDescription': project.pluginDescription + ) + } + + from(sourceSets.main.resources.srcDirs) { + exclude 'META-INF/mods.toml' + } +} + +shadowJar { + archiveFileName = "spark-${project.pluginVersion}-neoforge.jar" + configurations = [project.configurations.shade] + + relocate 'net.kyori.adventure', 'me.lucko.spark.lib.adventure' + relocate 'net.kyori.examination', 'me.lucko.spark.lib.adventure.examination' + relocate 'net.bytebuddy', 'me.lucko.spark.lib.bytebuddy' + relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' + relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' + relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' + relocate 'org.java_websocket', 'me.lucko.spark.lib.bytesocks.ws' + + exclude 'module-info.class' + exclude 'META-INF/maven/**' + exclude 'META-INF/proguard/**' +} + +artifacts { + archives shadowJar + shadow shadowJar +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeClassSourceLookup.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeClassSourceLookup.java new file mode 100644 index 00000000..5e60ee75 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeClassSourceLookup.java @@ -0,0 +1,36 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import cpw.mods.modlauncher.TransformingClassLoader; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; + +public class NeoForgeClassSourceLookup implements ClassSourceLookup { + + @Override + public String identify(Class clazz) { + if (clazz.getClassLoader() instanceof TransformingClassLoader) { + String name = clazz.getModule().getName(); + return name.equals("forge") || name.equals("minecraft") ? null : name; + } + return null; + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeCommandSender.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeCommandSender.java new file mode 100644 index 00000000..2f6a4110 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeCommandSender.java @@ -0,0 +1,77 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import me.lucko.spark.common.command.sender.AbstractCommandSender; +import me.lucko.spark.neoforge.plugin.NeoForgeSparkPlugin; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.commands.CommandSource; +import net.minecraft.network.chat.Component.Serializer; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.rcon.RconConsoleSource; +import net.minecraft.world.entity.player.Player; + +import java.util.Objects; +import java.util.UUID; + +public class NeoForgeCommandSender extends AbstractCommandSender { + private final NeoForgeSparkPlugin plugin; + + public NeoForgeCommandSender(CommandSource source, NeoForgeSparkPlugin plugin) { + super(source); + this.plugin = plugin; + } + + @Override + public String getName() { + if (super.delegate instanceof Player) { + return ((Player) super.delegate).getGameProfile().getName(); + } else if (super.delegate instanceof MinecraftServer) { + return "Console"; + } else if (super.delegate instanceof RconConsoleSource) { + return "RCON Console"; + } else { + return "unknown:" + super.delegate.getClass().getSimpleName(); + } + } + + @Override + public UUID getUniqueId() { + if (super.delegate instanceof Player) { + return ((Player) super.delegate).getUUID(); + } + return null; + } + + @Override + public void sendMessage(Component message) { + MutableComponent component = Serializer.fromJson(GsonComponentSerializer.gson().serializeToTree(message)); + Objects.requireNonNull(component, "component"); + super.delegate.sendSystemMessage(component); + } + + @Override + public boolean hasPermission(String permission) { + return this.plugin.hasPermission(super.delegate, permission); + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeExtraMetadataProvider.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeExtraMetadataProvider.java new file mode 100644 index 00000000..e18b3d31 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeExtraMetadataProvider.java @@ -0,0 +1,73 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import me.lucko.spark.common.platform.MetadataProvider; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.repository.PackSource; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class NeoForgeExtraMetadataProvider implements MetadataProvider { + + private final PackRepository resourcePackManager; + + public NeoForgeExtraMetadataProvider(PackRepository resourcePackManager) { + this.resourcePackManager = resourcePackManager; + } + + @Override + public Map get() { + Map metadata = new LinkedHashMap<>(); + metadata.put("datapacks", datapackMetadata()); + return metadata; + } + + private JsonElement datapackMetadata() { + JsonObject datapacks = new JsonObject(); + for (Pack profile : this.resourcePackManager.getSelectedPacks()) { + JsonObject obj = new JsonObject(); + obj.addProperty("name", profile.getTitle().getString()); + obj.addProperty("description", profile.getDescription().getString()); + obj.addProperty("source", resourcePackSource(profile.getPackSource())); + datapacks.add(profile.getId(), obj); + } + return datapacks; + } + + private static String resourcePackSource(PackSource source) { + if (source == PackSource.DEFAULT) { + return "none"; + } else if (source == PackSource.BUILT_IN) { + return "builtin"; + } else if (source == PackSource.WORLD) { + return "world"; + } else if (source == PackSource.SERVER) { + return "server"; + } else { + return "unknown"; + } + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgePlatformInfo.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgePlatformInfo.java new file mode 100644 index 00000000..ea0141ed --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgePlatformInfo.java @@ -0,0 +1,53 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import me.lucko.spark.common.platform.PlatformInfo; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.internal.versions.neoform.NeoFormVersion; + +public class NeoForgePlatformInfo implements PlatformInfo { + private final Type type; + + public NeoForgePlatformInfo(Type type) { + this.type = type; + } + + @Override + public Type getType() { + return this.type; + } + + @Override + public String getName() { + return "NeoForge"; + } + + @Override + public String getVersion() { + return NeoForgeVersion.getVersion(); + } + + @Override + public String getMinecraftVersion() { + return NeoFormVersion.getMCVersion(); + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgePlayerPingProvider.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgePlayerPingProvider.java new file mode 100644 index 00000000..191b60ec --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgePlayerPingProvider.java @@ -0,0 +1,45 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import com.google.common.collect.ImmutableMap; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Map; + +public class NeoForgePlayerPingProvider implements PlayerPingProvider { + private final MinecraftServer server; + + public NeoForgePlayerPingProvider(MinecraftServer server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ServerPlayer player : this.server.getPlayerList().getPlayers()) { + builder.put(player.getGameProfile().getName(), player.connection.latency()); + } + return builder.build(); + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeServerConfigProvider.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeServerConfigProvider.java new file mode 100644 index 00000000..813683d3 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeServerConfigProvider.java @@ -0,0 +1,56 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import me.lucko.spark.common.platform.serverconfig.ConfigParser; +import me.lucko.spark.common.platform.serverconfig.PropertiesConfigParser; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; + +import java.util.Collection; +import java.util.Map; + +public class NeoForgeServerConfigProvider extends ServerConfigProvider { + + /** A map of provided files and their type */ + private static final Map FILES; + /** A collection of paths to be excluded from the files */ + private static final Collection HIDDEN_PATHS; + + public NeoForgeServerConfigProvider() { + super(FILES, HIDDEN_PATHS); + } + + static { + ImmutableSet.Builder hiddenPaths = ImmutableSet.builder() + .add("server-ip") + .add("motd") + .add("resource-pack") + .add("rconpassword") + .add("level-seed") + .addAll(getSystemPropertyList("spark.serverconfigs.hiddenpaths")); + + FILES = ImmutableMap.of("server.properties", PropertiesConfigParser.INSTANCE); + HIDDEN_PATHS = hiddenPaths.build(); + } + +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeSparkMod.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeSparkMod.java new file mode 100644 index 00000000..16c19ec2 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeSparkMod.java @@ -0,0 +1,80 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import me.lucko.spark.neoforge.plugin.NeoForgeClientSparkPlugin; +import me.lucko.spark.neoforge.plugin.NeoForgeServerSparkPlugin; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.IExtensionPoint; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; + +import java.nio.file.Path; + +@Mod("spark") +public class NeoForgeSparkMod { + + private ModContainer container; + private Path configDirectory; + + private IEventBus eventBus; + + public NeoForgeSparkMod(IEventBus eventBus) { + this.eventBus = eventBus; + eventBus.addListener(this::setup); + eventBus.addListener(this::clientInit); + NeoForge.EVENT_BUS.register(this); + + ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, (a, b) -> true)); + } + + public String getVersion() { + return this.container.getModInfo().getVersion().toString(); + } + + public void setup(FMLCommonSetupEvent e) { + this.container = ModLoadingContext.get().getActiveContainer(); + this.configDirectory = FMLPaths.CONFIGDIR.get().resolve(this.container.getModId()); + } + + public void clientInit(FMLClientSetupEvent e) { + NeoForgeClientSparkPlugin.register(this, e); + } + + @SubscribeEvent + public void serverInit(ServerAboutToStartEvent e) { + NeoForgeServerSparkPlugin.register(this, e); + } + + public Path getConfigDirectory() { + if (this.configDirectory == null) { + throw new IllegalStateException("Config directory not set"); + } + return this.configDirectory; + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeTickHook.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeTickHook.java new file mode 100644 index 00000000..84e1aff2 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeTickHook.java @@ -0,0 +1,73 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import me.lucko.spark.common.tick.AbstractTickHook; +import me.lucko.spark.common.tick.TickHook; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.TickEvent; + +public class NeoForgeTickHook extends AbstractTickHook implements TickHook { + private final TickEvent.Type type; + + public NeoForgeTickHook(TickEvent.Type type) { + this.type = type; + } + + @SubscribeEvent + public void onTick(TickEvent.ServerTickEvent e) { + if (e.phase != TickEvent.Phase.START) { + return; + } + + if (e.type != this.type) { + return; + } + + onTick(); + } + + @SubscribeEvent + public void onTick(TickEvent.ClientTickEvent e) { + if (e.phase != TickEvent.Phase.START) { + return; + } + + if (e.type != this.type) { + return; + } + + onTick(); + } + + + @Override + public void start() { + NeoForge.EVENT_BUS.register(this); + } + + @Override + public void close() { + NeoForge.EVENT_BUS.unregister(this); + } + +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeTickReporter.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeTickReporter.java new file mode 100644 index 00000000..be61d3ef --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeTickReporter.java @@ -0,0 +1,72 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import me.lucko.spark.common.tick.SimpleTickReporter; +import me.lucko.spark.common.tick.TickReporter; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.TickEvent; + +public class NeoForgeTickReporter extends SimpleTickReporter implements TickReporter { + private final TickEvent.Type type; + + public NeoForgeTickReporter(TickEvent.Type type) { + this.type = type; + } + + @SubscribeEvent + public void onTick(TickEvent.ServerTickEvent e) { + if (e.type != this.type) { + return; + } + + switch (e.phase) { + case START -> onStart(); + case END -> onEnd(); + default -> throw new AssertionError(e.phase); + } + } + @SubscribeEvent + public void onTick(TickEvent.ClientTickEvent e) { + if (e.type != this.type) { + return; + } + + switch (e.phase) { + case START -> onStart(); + case END -> onEnd(); + default -> throw new AssertionError(e.phase); + } + } + + @Override + public void start() { + NeoForge.EVENT_BUS.register(this); + } + + @Override + public void close() { + NeoForge.EVENT_BUS.unregister(this); + super.close(); + } + +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeWorldInfoProvider.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeWorldInfoProvider.java new file mode 100644 index 00000000..ef76646c --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/NeoForgeWorldInfoProvider.java @@ -0,0 +1,172 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge; + +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import me.lucko.spark.common.platform.world.AbstractChunkInfo; +import me.lucko.spark.common.platform.world.CountMap; +import me.lucko.spark.common.platform.world.WorldInfoProvider; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.entity.EntityLookup; +import net.minecraft.world.level.entity.EntitySection; +import net.minecraft.world.level.entity.EntitySectionStorage; +import net.minecraft.world.level.entity.PersistentEntitySectionManager; +import net.minecraft.world.level.entity.TransientEntitySectionManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Stream; + +public abstract class NeoForgeWorldInfoProvider implements WorldInfoProvider { + + protected List getChunksFromCache(EntitySectionStorage cache) { + LongSet loadedChunks = cache.getAllChunksWithExistingSections(); + List list = new ArrayList<>(loadedChunks.size()); + + for (LongIterator iterator = loadedChunks.iterator(); iterator.hasNext(); ) { + long chunkPos = iterator.nextLong(); + Stream> sections = cache.getExistingSectionsInChunk(chunkPos); + + list.add(new ForgeChunkInfo(chunkPos, sections)); + } + + return list; + } + + public static final class Server extends NeoForgeWorldInfoProvider { + private final MinecraftServer server; + + public Server(MinecraftServer server) { + this.server = server; + } + + @Override + public CountsResult pollCounts() { + int players = this.server.getPlayerCount(); + int entities = 0; + int chunks = 0; + + for (ServerLevel level : this.server.getAllLevels()) { + PersistentEntitySectionManager entityManager = level.entityManager; + EntityLookup entityIndex = entityManager.visibleEntityStorage; + + entities += entityIndex.count(); + chunks += level.getChunkSource().getLoadedChunksCount(); + } + + return new CountsResult(players, entities, -1, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); + + for (ServerLevel level : this.server.getAllLevels()) { + PersistentEntitySectionManager entityManager = level.entityManager; + EntitySectionStorage cache = entityManager.sectionStorage; + + List list = getChunksFromCache(cache); + data.put(level.dimension().location().getPath(), list); + } + + return data; + } + } + + public static final class Client extends NeoForgeWorldInfoProvider { + private final Minecraft client; + + public Client(Minecraft client) { + this.client = client; + } + + @Override + public CountsResult pollCounts() { + ClientLevel level = this.client.level; + if (level == null) { + return null; + } + + TransientEntitySectionManager entityManager = level.entityStorage; + EntityLookup entityIndex = entityManager.entityStorage; + + int entities = entityIndex.count(); + int chunks = level.getChunkSource().getLoadedChunksCount(); + + return new CountsResult(-1, entities, -1, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); + + ClientLevel level = this.client.level; + if (level == null) { + return null; + } + + TransientEntitySectionManager entityManager = level.entityStorage; + EntitySectionStorage cache = entityManager.sectionStorage; + + List list = getChunksFromCache(cache); + data.put(level.dimension().location().getPath(), list); + + return data; + } + } + + public static final class ForgeChunkInfo extends AbstractChunkInfo> { + private final CountMap> entityCounts; + + ForgeChunkInfo(long chunkPos, Stream> entities) { + super(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos)); + + this.entityCounts = new CountMap.Simple<>(new HashMap<>()); + entities.forEach(section -> { + if (section.getStatus().isAccessible()) { + section.getEntities().forEach(entity -> + this.entityCounts.increment(entity.getType()) + ); + } + }); + } + + @Override + public CountMap> getEntityCounts() { + return this.entityCounts; + } + + @Override + public String entityTypeName(EntityType type) { + return EntityType.getKey(type).toString(); + } + } + + +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeClientSparkPlugin.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeClientSparkPlugin.java new file mode 100644 index 00000000..92bd6563 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeClientSparkPlugin.java @@ -0,0 +1,153 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge.plugin; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import me.lucko.spark.common.platform.MetadataProvider; +import me.lucko.spark.common.platform.PlatformInfo; +import me.lucko.spark.common.platform.world.WorldInfoProvider; +import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.tick.TickHook; +import me.lucko.spark.common.tick.TickReporter; +import me.lucko.spark.neoforge.NeoForgeCommandSender; +import me.lucko.spark.neoforge.NeoForgeExtraMetadataProvider; +import me.lucko.spark.neoforge.NeoForgePlatformInfo; +import me.lucko.spark.neoforge.NeoForgeSparkMod; +import me.lucko.spark.neoforge.NeoForgeTickHook; +import me.lucko.spark.neoforge.NeoForgeTickReporter; +import me.lucko.spark.neoforge.NeoForgeWorldInfoProvider; +import net.minecraft.client.Minecraft; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.TickEvent; + +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +public class NeoForgeClientSparkPlugin extends NeoForgeSparkPlugin implements Command, SuggestionProvider { + + public static void register(NeoForgeSparkMod mod, FMLClientSetupEvent event) { + NeoForgeClientSparkPlugin plugin = new NeoForgeClientSparkPlugin(mod, Minecraft.getInstance()); + plugin.enable(); + } + + private final Minecraft minecraft; + private final ThreadDumper gameThreadDumper; + + public NeoForgeClientSparkPlugin(NeoForgeSparkMod mod, Minecraft minecraft) { + super(mod); + this.minecraft = minecraft; + this.gameThreadDumper = new ThreadDumper.Specific(minecraft.gameThread); + } + + @Override + public void enable() { + super.enable(); + + // register listeners + NeoForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onCommandRegister(RegisterClientCommandsEvent e) { + registerCommands(e.getDispatcher(), this, this, "sparkc", "sparkclient"); + } + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + String[] args = processArgs(context, false, "sparkc", "sparkclient"); + if (args == null) { + return 0; + } + + this.platform.executeCommand(new NeoForgeCommandSender(context.getSource().getEntity(), this), args); + return Command.SINGLE_SUCCESS; + } + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + String[] args = processArgs(context, true, "/sparkc", "/sparkclient"); + if (args == null) { + return Suggestions.empty(); + } + + return generateSuggestions(new NeoForgeCommandSender(context.getSource().getEntity(), this), args, builder); + } + + @Override + public boolean hasPermission(CommandSource sender, String permission) { + return true; + } + + @Override + public Stream getCommandSenders() { + return Stream.of(new NeoForgeCommandSender(this.minecraft.player, this)); + } + + @Override + public void executeSync(Runnable task) { + this.minecraft.executeIfPossible(task); + } + + @Override + public ThreadDumper getDefaultThreadDumper() { + return this.gameThreadDumper; + } + + @Override + public TickHook createTickHook() { + return new NeoForgeTickHook(TickEvent.Type.CLIENT); + } + + @Override + public TickReporter createTickReporter() { + return new NeoForgeTickReporter(TickEvent.Type.CLIENT); + } + + @Override + public WorldInfoProvider createWorldInfoProvider() { + return new NeoForgeWorldInfoProvider.Client(this.minecraft); + } + + @Override + public MetadataProvider createExtraMetadataProvider() { + return new NeoForgeExtraMetadataProvider(this.minecraft.getResourcePackRepository()); + } + + @Override + public PlatformInfo getPlatformInfo() { + return new NeoForgePlatformInfo(PlatformInfo.Type.CLIENT); + } + + @Override + public String getCommandName() { + return "sparkc"; + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeServerSparkPlugin.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeServerSparkPlugin.java new file mode 100644 index 00000000..94522c12 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeServerSparkPlugin.java @@ -0,0 +1,266 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge.plugin; + +import com.google.common.collect.ImmutableMap; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import me.lucko.spark.common.platform.MetadataProvider; +import me.lucko.spark.common.platform.PlatformInfo; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; +import me.lucko.spark.common.platform.world.WorldInfoProvider; +import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.tick.TickHook; +import me.lucko.spark.common.tick.TickReporter; +import me.lucko.spark.neoforge.NeoForgeCommandSender; +import me.lucko.spark.neoforge.NeoForgeExtraMetadataProvider; +import me.lucko.spark.neoforge.NeoForgePlatformInfo; +import me.lucko.spark.neoforge.NeoForgePlayerPingProvider; +import me.lucko.spark.neoforge.NeoForgeServerConfigProvider; +import me.lucko.spark.neoforge.NeoForgeSparkMod; +import me.lucko.spark.neoforge.NeoForgeTickHook; +import me.lucko.spark.neoforge.NeoForgeTickReporter; +import me.lucko.spark.neoforge.NeoForgeWorldInfoProvider; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.TickEvent; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.server.permission.PermissionAPI; +import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class NeoForgeServerSparkPlugin extends NeoForgeSparkPlugin implements Command, SuggestionProvider { + + public static void register(NeoForgeSparkMod mod, ServerAboutToStartEvent event) { + NeoForgeServerSparkPlugin plugin = new NeoForgeServerSparkPlugin(mod, event.getServer()); + plugin.enable(); + } + + private static final PermissionNode.PermissionResolver DEFAULT_PERMISSION_VALUE = (player, playerUUID, context) -> { + if (player == null) { + return false; + } + + MinecraftServer server = player.getServer(); + if (server != null && server.isSingleplayerOwner(player.getGameProfile())) { + return true; + } + + return player.hasPermissions(4); + }; + + private final MinecraftServer server; + private final ThreadDumper gameThreadDumper; + private Map> registeredPermissions = Collections.emptyMap(); + + public NeoForgeServerSparkPlugin(NeoForgeSparkMod mod, MinecraftServer server) { + super(mod); + this.server = server; + this.gameThreadDumper = new ThreadDumper.Specific(server.getRunningThread()); + } + + @Override + public void enable() { + super.enable(); + + // register commands + registerCommands(this.server.getCommands().getDispatcher()); + + // register listeners + NeoForge.EVENT_BUS.register(this); + } + + @Override + public void disable() { + super.disable(); + + // unregister listeners + NeoForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onDisable(ServerStoppingEvent event) { + if (event.getServer() == this.server) { + disable(); + } + } + + @SubscribeEvent + public void onPermissionGather(PermissionGatherEvent.Nodes e) { + // collect all possible permissions + List permissions = this.platform.getCommands().stream() + .map(me.lucko.spark.common.command.Command::primaryAlias) + .collect(Collectors.toList()); + + // special case for the "spark" permission: map it to "spark.all" + permissions.add("all"); + + // register permissions with forge & keep a copy for lookup + ImmutableMap.Builder> builder = ImmutableMap.builder(); + + Map> alreadyRegistered = e.getNodes().stream().collect(Collectors.toMap(PermissionNode::getNodeName, Function.identity())); + + for (String permission : permissions) { + String permissionString = "spark." + permission; + + // there's a weird bug where it seems that this listener can be called twice, causing an + // IllegalArgumentException to be thrown the second time e.addNodes is called. + PermissionNode existing = alreadyRegistered.get(permissionString); + if (existing != null) { + //noinspection unchecked + builder.put(permissionString, (PermissionNode) existing); + continue; + } + + PermissionNode node = new PermissionNode<>("spark", permission, PermissionTypes.BOOLEAN, DEFAULT_PERMISSION_VALUE); + e.addNodes(node); + builder.put(permissionString, node); + } + this.registeredPermissions = builder.build(); + } + + @SubscribeEvent + public void onCommandRegister(RegisterCommandsEvent e) { + registerCommands(e.getDispatcher()); + } + + private void registerCommands(CommandDispatcher dispatcher) { + registerCommands(dispatcher, this, this, "spark"); + } + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + String[] args = processArgs(context, false, "/spark", "spark"); + if (args == null) { + return 0; + } + + CommandSource source = context.getSource().getEntity() != null ? context.getSource().getEntity() : context.getSource().getServer(); + this.platform.executeCommand(new NeoForgeCommandSender(source, this), args); + return Command.SINGLE_SUCCESS; + } + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + String[] args = processArgs(context, true, "/spark", "spark"); + if (args == null) { + return Suggestions.empty(); + } + + return generateSuggestions(new NeoForgeCommandSender(context.getSource().getPlayerOrException(), this), args, builder); + } + + @Override + public boolean hasPermission(CommandSource sender, String permission) { + if (sender instanceof ServerPlayer) { + if (permission.equals("spark")) { + permission = "spark.all"; + } + + PermissionNode permissionNode = this.registeredPermissions.get(permission); + if (permissionNode == null) { + throw new IllegalStateException("spark permission not registered: " + permission); + } + return PermissionAPI.getPermission((ServerPlayer) sender, permissionNode); + } else { + return true; + } + } + + @Override + public Stream getCommandSenders() { + return Stream.concat( + this.server.getPlayerList().getPlayers().stream(), + Stream.of(this.server) + ).map(sender -> new NeoForgeCommandSender(sender, this)); + } + + @Override + public void executeSync(Runnable task) { + this.server.executeIfPossible(task); + } + + @Override + public ThreadDumper getDefaultThreadDumper() { + return this.gameThreadDumper; + } + + @Override + public TickHook createTickHook() { + return new NeoForgeTickHook(TickEvent.Type.SERVER); + } + + @Override + public TickReporter createTickReporter() { + return new NeoForgeTickReporter(TickEvent.Type.SERVER); + } + + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new NeoForgePlayerPingProvider(this.server); + } + + @Override + public ServerConfigProvider createServerConfigProvider() { + return new NeoForgeServerConfigProvider(); + } + + @Override + public MetadataProvider createExtraMetadataProvider() { + return new NeoForgeExtraMetadataProvider(this.server.getPackRepository()); + } + + @Override + public WorldInfoProvider createWorldInfoProvider() { + return new NeoForgeWorldInfoProvider.Server(this.server); + } + + @Override + public PlatformInfo getPlatformInfo() { + return new NeoForgePlatformInfo(PlatformInfo.Type.SERVER); + } + + @Override + public String getCommandName() { + return "spark"; + } +} diff --git a/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeSparkPlugin.java b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeSparkPlugin.java new file mode 100644 index 00000000..43d77a16 --- /dev/null +++ b/spark-neoforge/src/main/java/me/lucko/spark/neoforge/plugin/NeoForgeSparkPlugin.java @@ -0,0 +1,169 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.neoforge.plugin; + +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 com.mojang.brigadier.tree.LiteralCommandNode; +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.command.sender.CommandSender; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; +import me.lucko.spark.common.util.SparkThreadFactory; +import me.lucko.spark.neoforge.NeoForgeClassSourceLookup; +import me.lucko.spark.neoforge.NeoForgeSparkMod; +import net.minecraft.commands.CommandSource; +import net.neoforged.fml.ModList; +import net.neoforged.neoforgespi.language.IModInfo; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Level; + +public abstract class NeoForgeSparkPlugin implements SparkPlugin { + + private final NeoForgeSparkMod mod; + private final Logger logger; + protected final ScheduledExecutorService scheduler; + + protected SparkPlatform platform; + + protected NeoForgeSparkPlugin(NeoForgeSparkMod mod) { + this.mod = mod; + this.logger = LogManager.getLogger("spark"); + this.scheduler = Executors.newScheduledThreadPool(4, new SparkThreadFactory()); + } + + public void enable() { + this.platform = new SparkPlatform(this); + this.platform.enable(); + } + + public void disable() { + this.platform.disable(); + this.scheduler.shutdown(); + } + + public abstract boolean hasPermission(CommandSource sender, String permission); + + @Override + public String getVersion() { + return this.mod.getVersion(); + } + + @Override + public Path getPluginDirectory() { + return this.mod.getConfigDirectory(); + } + + @Override + public void executeAsync(Runnable task) { + this.scheduler.execute(task); + } + + @Override + public void log(Level level, String msg) { + if (level == Level.INFO) { + this.logger.info(msg); + } else if (level == Level.WARNING) { + this.logger.warn(msg); + } else if (level == Level.SEVERE) { + this.logger.error(msg); + } else { + throw new IllegalArgumentException(level.getName()); + } + } + + @Override + public ClassSourceLookup createClassSourceLookup() { + return new NeoForgeClassSourceLookup(); + } + + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + ModList.get().getMods(), + IModInfo::getModId, + mod -> mod.getVersion().toString(), + mod -> null // ? + ); + } + + protected CompletableFuture generateSuggestions(CommandSender sender, String[] args, SuggestionsBuilder builder) { + SuggestionsBuilder suggestions; + + int lastSpaceIdx = builder.getRemaining().lastIndexOf(' '); + if (lastSpaceIdx != -1) { + suggestions = builder.createOffset(builder.getStart() + lastSpaceIdx + 1); + } else { + suggestions = builder; + } + + return CompletableFuture.supplyAsync(() -> { + for (String suggestion : this.platform.tabCompleteCommand(sender, args)) { + suggestions.suggest(suggestion); + } + return suggestions.build(); + }); + } + + protected static void registerCommands(CommandDispatcher dispatcher, Command executor, SuggestionProvider suggestor, String... aliases) { + if (aliases.length == 0) { + return; + } + + String mainName = aliases[0]; + LiteralArgumentBuilder command = LiteralArgumentBuilder.literal(mainName) + .executes(executor) + .then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()) + .suggests(suggestor) + .executes(executor) + ); + + LiteralCommandNode node = dispatcher.register(command); + for (int i = 1; i < aliases.length; i++) { + dispatcher.register(LiteralArgumentBuilder.literal(aliases[i]).redirect(node)); + } + } + + protected static String[] processArgs(CommandContext context, boolean tabComplete, String... aliases) { + String[] split = context.getInput().split(" ", tabComplete ? -1 : 0); + if (split.length == 0 || !Arrays.asList(aliases).contains(split[0])) { + return null; + } + + return Arrays.copyOfRange(split, 1, split.length); + } +} diff --git a/spark-neoforge/src/main/resources/META-INF/accesstransformer.cfg b/spark-neoforge/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 00000000..43d14fc6 --- /dev/null +++ b/spark-neoforge/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,7 @@ +public net.minecraft.server.level.ServerLevel entityManager # entityManager +public net.minecraft.world.level.entity.PersistentEntitySectionManager sectionStorage # sectionStorage +public net.minecraft.world.level.entity.PersistentEntitySectionManager visibleEntityStorage # visibleEntityStorage +public net.minecraft.client.multiplayer.ClientLevel entityStorage # entityStorage +public net.minecraft.world.level.entity.TransientEntitySectionManager sectionStorage # sectionStorage +public net.minecraft.world.level.entity.TransientEntitySectionManager entityStorage # entityStorage +public net.minecraft.client.Minecraft gameThread # gameThread diff --git a/spark-neoforge/src/main/resources/META-INF/mods.toml b/spark-neoforge/src/main/resources/META-INF/mods.toml new file mode 100644 index 00000000..f44574d2 --- /dev/null +++ b/spark-neoforge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,20 @@ +modLoader="javafml" +loaderVersion="[2,)" +authors="Luck" +license="GPLv3" + +[[mods]] +modId="spark" +displayName="spark" +version="${pluginVersion}" +description="${pluginDescription}" + +[[accessTransformers]] +file="META-INF/accesstransformer.cfg" + +[[dependencies.spark]] +modId = "neoforge" +mandatory=true +versionRange = "[20,)" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/spark-neoforge/src/main/resources/pack.mcmeta b/spark-neoforge/src/main/resources/pack.mcmeta new file mode 100644 index 00000000..d34a5b78 --- /dev/null +++ b/spark-neoforge/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "spark resources", + "pack_format": 6 + } +}