diff --git a/README.md b/README.md index 680a69b..25d28c4 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,4 @@ You can find all our modules if you wish to modify them yourself below. - [paynow-sponge](./paynow-sponge) - [paynow-velocity](./paynow-velocity) - [paynow-fabric](./paynow-fabric) +- [paynow-neoforge](./paynow-neoforge) \ No newline at end of file diff --git a/paynow-neoforge/build.gradle b/paynow-neoforge/build.gradle new file mode 100644 index 0000000..9986e51 --- /dev/null +++ b/paynow-neoforge/build.gradle @@ -0,0 +1,152 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.gradleup.shadow' version '9.3.1' + id 'net.neoforged.gradle.userdev' version '7.1.38' +} + +sourceSets.main.resources { + // Include resources generated by data generators. + srcDir('src/generated/resources') + + // Exclude common development only resources from finalized outputs + exclude("**/*.bbmodel") // BlockBench project files + exclude("src/generated/**/.cache") // datagen cache files +} + +repositories { + // Add here additional repositories if required by some of the dependencies below. +} + +configurations { + runtimeClasspath.extendsFrom localRuntime + shadowed + implementation.extendsFrom shadowed +} + +base { + archivesName = mod_id +} + +// Mojang ships Java 21 to end users in 1.21.1, so mods should target Java 21. +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +//minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') +//minecraft.accessTransformers.entry public net.minecraft.client.Minecraft textureManager # textureManager + +// Default run configurations. +// These can be tweaked, removed, or duplicated as needed. +runs { + // applies to all the run configs below + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + systemProperty 'forge.logging.console.level', 'debug' + + workingDirectory project.layout.projectDirectory.dir('run').dir(name) + + modSource project.sourceSets.main + } + + client { + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + argument '--nogui' + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // workingDirectory project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + arguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } +} + +dependencies { + implementation "net.neoforged:neoforge:${neo_version}" + + shadowed(implementation project(":paynow-lib")) +} + +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. +// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : version, + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} + +shadowJar { + configurations = [project.configurations.shadowed] + archiveClassifier.set('') // This is required to ensure that the shadow jar is the default jar output of the project. + exclude 'net/minecraft/**' +} + +// 3. Inform Gradle's assembling engine to favor the Shadow output over the normal jar +tasks.named('jar', Jar) { + // This tells Gradle to skip the vanilla jar task since shadowJar will generate it + finalizedBy tasks.shadowJar +} + +// Ensure assembling lifecycle triggers your shadow processing +assemble.dependsOn shadowJar + +// Example configuration to allow publishing using the maven-publish plugin +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url "file://${project.projectDir}/repo" + } + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} diff --git a/paynow-neoforge/gradle.properties b/paynow-neoforge/gradle.properties new file mode 100644 index 0000000..1e6b41f --- /dev/null +++ b/paynow-neoforge/gradle.properties @@ -0,0 +1,33 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +org.gradle.jvmargs=-Xmx1G +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true + +#read more on this at https://github.com/neoforged/NeoGradle/blob/NG_7.0/README.md#apply-parchment-mappings +# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started +neogradle.subsystems.parchment.minecraftVersion=1.21.1 +neogradle.subsystems.parchment.mappingsVersion=2024.11.17 +# Environment Properties +# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge +# The Minecraft version must agree with the Neo version to get a valid artifact +minecraft_version=1.21.1 +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +minecraft_version_range=[1.21.1] +# The Neo version must agree with the Minecraft version to get a valid artifact +neo_version=21.1.234 +# The loader version range can only use the major version of FML as bounds +loader_version_range=[1,) + +## Mod Properties + +# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} +# Must match the String constant located in the main mod class annotated with @Mod. +mod_id=paynow_neoforge +# The human-readable display name for the mod. +mod_name=PayNow NeoForge +# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. +mod_license=MIT diff --git a/paynow-neoforge/src/main/java/gg/paynow/paynowneoforge/PayNowNeoForge.java b/paynow-neoforge/src/main/java/gg/paynow/paynowneoforge/PayNowNeoForge.java new file mode 100644 index 0000000..22e3717 --- /dev/null +++ b/paynow-neoforge/src/main/java/gg/paynow/paynowneoforge/PayNowNeoForge.java @@ -0,0 +1,135 @@ +package gg.paynow.paynowneoforge; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import gg.paynow.paynowlib.PayNowLib; +import gg.paynow.paynowlib.PayNowUtils; +import gg.paynow.paynowlib.events.PayNowEvent; +import gg.paynow.paynowlib.events.PlayerJoinEventData; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; +import org.slf4j.Logger; + +import com.mojang.logging.LogUtils; + +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.server.ServerStartingEvent; + +import java.io.File; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; + +@Mod(PayNowNeoForge.MODID) +public class PayNowNeoForge { + public static final String MODID = "paynow_neoforge"; + public static final Logger LOGGER = LogUtils.getLogger(); + + private static PayNowNeoForge instance; + private MinecraftServer server; + private PayNowLib payNowLib; + + private int lastCheck = 0; + private int lastEventsCheck = 0; + + public PayNowNeoForge() { + instance = this; + // Register ourselves for server and other game events we are interested in. + // Note that this is necessary if and only if we want *this* class (ExampleMod) to respond directly to events. + // Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below. + NeoForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onCommandsRegistration(RegisterCommandsEvent event) { + event.getDispatcher().register(PayNowNeoForgeCommand.generateCommand(this)); + } + + @SubscribeEvent + public void onServerStarting(ServerStartingEvent event) { + server = event.getServer(); + String motd = server.getMotd(); + String serverIp = server.getLocalIp(); + + this.payNowLib = new PayNowLib(command -> CompletableFuture.supplyAsync(() -> { + try { + server.getCommands().getDispatcher().execute(command, server.createCommandSourceStack()); + } catch (CommandSyntaxException ignored) {} + return true; // Assume the command always succeeds, else it gets stuck. + }, server).join(), Objects.equals(serverIp, "") ? "0.0.0.0" : serverIp + ":" + server.getPort(), Objects.equals(motd, "") ? "NeoForge Server" : motd); + + this.payNowLib.setLogCallback((s, level) -> { + if (level == Level.SEVERE) { + LOGGER.error(s); + } else if(level == Level.WARNING) { + LOGGER.warn(s); + } else { + LOGGER.info(s); + } + }); + + this.payNowLib.loadPayNowConfig(this.getConfigFile()); + } + + @SubscribeEvent + public void onServerTick(ServerTickEvent.Post event) { + if(lastCheck > 0) { + lastCheck--; + } else { + lastCheck = this.payNowLib.getConfig().getApiCheckInterval() * 20; + this.check(); + } + + if(lastEventsCheck > 0) { + lastEventsCheck--; + } else { + lastEventsCheck = this.payNowLib.getConfig().getEventsQueueReportInterval() * 20; + this.payNowLib.reportEvents(); + } + } + + @SubscribeEvent + public void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + if (event.getEntity().level().isClientSide) return; + ServerPlayer player = event.getEntity() instanceof ServerPlayer ? (ServerPlayer) event.getEntity() : null; + if (player == null) return; + String ip = player.getIpAddress(); + PayNowEvent payNowEvent = new PayNowEvent("player_join", new Date(), new PlayerJoinEventData(ip, player.getUUID())); + instance.getPayNowLib().registerEvent(payNowEvent); + } + + @SubscribeEvent + public void onServerStopping(ServerStoppingEvent event) { + server = null; + PayNowUtils.ASYNC_EXEC.shutdown(); + } + + private void check() { + List onlinePlayersName = new ArrayList<>(); + List onlinePlayersUUID = new ArrayList<>(); + for(ServerPlayer player : this.server.getPlayerList().getPlayers()) { + onlinePlayersName.add(player.getName().getString()); + onlinePlayersUUID.add(player.getUUID()); + } + payNowLib.fetchPendingCommands(onlinePlayersName, onlinePlayersUUID); + } + + public void triggerConfigUpdate(){ + this.payNowLib.savePayNowConfig(this.getConfigFile()); + this.payNowLib.updateConfig(); + } + + private File getConfigFile() { + return new File(FMLPaths.CONFIGDIR.get().toFile(), "paynow.json"); + } + + public PayNowLib getPayNowLib() { + return payNowLib; + } +} diff --git a/paynow-neoforge/src/main/java/gg/paynow/paynowneoforge/PayNowNeoForgeCommand.java b/paynow-neoforge/src/main/java/gg/paynow/paynowneoforge/PayNowNeoForgeCommand.java new file mode 100644 index 0000000..d95ea73 --- /dev/null +++ b/paynow-neoforge/src/main/java/gg/paynow/paynowneoforge/PayNowNeoForgeCommand.java @@ -0,0 +1,25 @@ +package gg.paynow.paynowneoforge; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import static com.mojang.brigadier.arguments.StringArgumentType.string; + +public class PayNowNeoForgeCommand { + public static LiteralArgumentBuilder generateCommand(PayNowNeoForge mod) { + return Commands.literal("paynow") + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("link") + .then(Commands.argument("token", string()) + .executes(context -> { + String token = context.getArgument("token", String.class); + mod.getPayNowLib().getConfig().setApiToken(token); + mod.triggerConfigUpdate(); + context.getSource().sendSystemMessage(Component.literal("API token updated").withStyle(ChatFormatting.RED)); + return 1; + }))); + } +} diff --git a/paynow-neoforge/src/main/resources/META-INF/neoforge.mods.toml b/paynow-neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..801c4c6 --- /dev/null +++ b/paynow-neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,95 @@ +# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml + +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader="javafml" #mandatory + +# A version range to match for said mod loader - for regular FML @Mod it will be the FML version. This is currently 2. +loaderVersion="${loader_version_range}" #mandatory + +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license="${mod_license}" + +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional + +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory + +# The modid of the mod +modId="${mod_id}" #mandatory + +# The version number of the mod +version="${mod_version}" #mandatory + +# A display name for the mod +displayName="${mod_name}" #mandatory + +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional + +# A URL for the "homepage" for this mod, displayed in the mod UI +#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional + +# A file name (in the root of the mod JAR) containing a logo for display +#logoFile="examplemod.png" #optional + +# A text field displayed in the mod UI +#credits="" #optional + +# The authors of the mod, displayed in the mod UI (optional) +#authors="" + +# The description text for the mod (multi line!) (#mandatory) +description=''' +Official plugin for the PayNow.gg store integration. +''' + +# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. +#[[mixins]] +#config="${mod_id}.mixins.json" + +# The [[accessTransformers]] block allows you to declare where your AT file is. +# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg +#[[accessTransformers]] +#file="META-INF/accesstransformer.cfg" + +# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json + +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies.${mod_id}]] #optional + # the modid of the dependency + modId="neoforge" #mandatory + # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). + # 'required' requires the mod to exist, 'optional' does not + # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning + type="required" #mandatory + # Optional field describing why the dependency is required or why it is incompatible + # reason="..." + # The version range of the dependency + versionRange="[${neo_version},)" #mandatory + # An ordering relationship for the dependency. + # BEFORE - This mod is loaded BEFORE the dependency + # AFTER - This mod is loaded AFTER the dependency + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT, or SERVER + side="BOTH" + +# Here's another dependency +[[dependencies.${mod_id}]] + modId="minecraft" + type="required" + # This version range declares a minimum of the current minecraft version up to but not including the next major version + versionRange="${minecraft_version_range}" + ordering="NONE" + side="BOTH" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features.${mod_id}] +#openGLVersion="[3.2,)" diff --git a/settings.gradle b/settings.gradle index 85f01c7..b421605 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,10 +4,18 @@ pluginManagement { name = 'Fabric' url = 'https://maven.fabricmc.net/' } + maven { + name = "Neoforge" + url = 'https://maven.neoforged.net/releases' + } gradlePluginPortal() } } +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + rootProject.name = 'paynow-minecraft' include 'paynow-lib' include 'paynow-bukkit-legacy' @@ -16,4 +24,5 @@ include 'paynow-bungeecord' include 'paynow-sponge' include 'paynow-velocity' include 'paynow-fabric' +include 'paynow-neoforge' include 'paynow-folia' \ No newline at end of file