7 Commits
Author SHA1 Message Date
nerjal 9d3b9f36a2 version bump 2026-10-21 17:57:41 +02:00
nerjal 0dc846ba07 Added dev env detection 2026-10-21 17:57:15 +02:00
nerjal 02408383e3 Fix mixin compat plugin logger name resolution 2026-10-21 17:49:08 +02:00
nerjal d24c1ec17c Removed pointless code 2026-10-20 16:00:39 +02:00
nerjal 5192a5258b Now loader-agnostic 2026-10-20 12:42:59 +02:00
nerjal 9f5b16756a Now with a README 2026-10-19 13:22:32 +02:00
nerjal 5188e4dcf5 Patch releasing to "/releases" 2026-10-19 13:22:14 +02:00
19 changed files with 729 additions and 75 deletions

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
# Features
## Platform detection
Automatic detection of the current modloader (between Fabric, Forge, Neoforge or Other)
and of the current execution side (client or server) using the mixin engine.
Access these via the `PlatformService` class.
## Modding compat
A mod compatibility mixin plugin is made available at
`com.nerjal.moddingutils.compat.CompatMixinPLugin` and will use the `Compat` annotation
to allow you to make mixins only be applied with their respective dependencies are
loaded.
Dependencies need a mod ID at least, but can also include filtering base on authors names
as well as version ranges.
# Usage
Add the `https://maven.nerjal.com/releases/` maven repository to your project,
and import the `com.nerjal:ModdingUtils:<VERSION>` artifact in your project.
You are free to bundle this project into yours.
Don't hesitate to add a dependency to "the same main version as yours" in your project
manifest (e.g. `"moddingutils": "~${moddingutils_version}"`) to avoid trying to load
with breaking changes brought by some other project including a more recent version of
ModdingUtils.
## Latest version
[1.1](https://maven.nerjal.com/#/releases/com/nerjal/ModdingUtils/1.1)
+28 -6
View File
@@ -1,5 +1,5 @@
plugins { plugins {
id 'fabric-loom' version '1.17-SNAPSHOT' id 'java'
id 'maven-publish' id 'maven-publish'
id 'signing' id 'signing'
} }
@@ -12,12 +12,34 @@ base {
} }
repositories { repositories {
maven {
name = 'Fabric maven'
url = 'https://maven.fabricmc.net/'
}
maven {
name = 'NeoForge maven'
url = 'https://maven.neoforged.net/releases/'
}
maven {
name = "Forge maven"
url = "https://maven.minecraftforge.net/"
}
maven {
name = 'Minecraft maven'
url = 'https://libraries.minecraft.net/'
}
} }
dependencies { dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}" implementation("io.github.llamalad7:mixinextras-fabric:${project.mixinextras_version}")
mappings loom.officialMojangMappings() compileOnly "net.fabricmc:fabric-loader:${project.fabric_version}"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" //noinspection VulnerableLibrariesLocal
compileOnly "net.neoforged:neoforge:${project.neoforge_version}"
// istg why does forge need this many different dependencies to ensure getting all the needed classes,
// couldn't they use transitive dependencies like everyone?
compileOnly "net.minecraftforge:fmlloader:${project.forge_version}"
compileOnly "net.minecraftforge:forge:${project.forge_version}:universal"
compileOnly "net.minecraftforge:fmlcore:${project.forge_version}"
} }
def targetJavaVersion = 17 def targetJavaVersion = 17
@@ -61,7 +83,7 @@ publishing {
from components.java from components.java
it.artifactId = project.maven_artifact it.artifactId = project.maven_artifact
it.version = "${project.mod_version}-${project.minecraft_version}" it.version = project.version
pom { pom {
packaging = "jar" packaging = "jar"
@@ -100,7 +122,7 @@ publishing {
repositories { repositories {
maven { maven {
name = "Nerjal" name = "Nerjal"
url = "https://maven.nerjal.com/snapshots" url = "https://maven.nerjal.com/releases"
credentials(PasswordCredentials.class) credentials(PasswordCredentials.class)
authentication { authentication {
it.create("basic", BasicAuthentication.class) it.create("basic", BasicAuthentication.class)
+6 -8
View File
@@ -1,11 +1,9 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.20.1 minecraft_version=1.20.1
loader_version=0.18.6 mixinextras_version=0.4.1
# Mod Properties fabric_version=0.18.6
mod_version=1.0 neoforge_version=20.2.3-beta
forge_version=1.20.1-47.0.19
mod_version=1.2
maven_group=com.nerjal maven_group=com.nerjal
maven_artifact=ModdingUtils maven_artifact=ModdingUtils
-4
View File
@@ -1,9 +1,5 @@
pluginManagement { pluginManagement {
repositories { repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal() gradlePluginPortal()
} }
} }
@@ -1,11 +1,7 @@
package com.nerjal.moddingutils.compat; package com.nerjal.moddingutils.compat;
import net.fabricmc.loader.api.FabricLoader; import com.nerjal.moddingutils.mod.ModVersion;
import net.fabricmc.loader.api.ModContainer; import com.nerjal.moddingutils.platform.PlatformService;
import net.fabricmc.loader.api.Version;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.api.metadata.Person;
import net.fabricmc.loader.impl.util.version.VersionParser;
import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -15,20 +11,37 @@ import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.service.MixinService;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
public class CompatMixinPlugin implements IMixinConfigPlugin { public class CompatMixinPlugin implements IMixinConfigPlugin {
Set<String> loadedCompatModIds = new HashSet<>(); Set<String> loadedCompatModIds = new HashSet<>();
Set<String> skippedModCompats = new HashSet<>(); Set<String> skippedModCompats = new HashSet<>();
Logger logger; Logger logger;
private static int lastIndexOf(String target, String[] array) {
int x = array.length - 1;
while (x >= 0) {
String s = array[x--];
if (Objects.equals(s, target)) break;
}
return x;
}
@Override @Override
public void onLoad(String mixinPackage) { public void onLoad(String mixinPackage) {
String[] split = mixinPackage.split("\\."); String[] split = mixinPackage.split("\\.");
int x = split.length; int x;
String s = split[--x]; String s = null;
while (s.isBlank() && x > 0) s = split[--x]; if ((x = lastIndexOf("mixin", split)) == -1) {
logger = LoggerFactory.getLogger("["+s+"/MixinCompat]"); if ((x = lastIndexOf("mixins", split)) == -1) {
x = split.length;
s = split[--x];
while ((s.isBlank()) && x > 0) s = split[--x];
}
}
if (s == null) {
s = split[x];
}
logger = LoggerFactory.getLogger(s+"/MixinCompat");
} }
@Override @Override
@@ -47,16 +60,16 @@ public class CompatMixinPlugin implements IMixinConfigPlugin {
if (!node.desc.equals(Compat.class.descriptorString())) continue; if (!node.desc.equals(Compat.class.descriptorString())) continue;
Compat c = Compat.Impl.ofNode(node); Compat c = Compat.Impl.ofNode(node);
List<Mod> mods = Arrays.stream(c.mods()).sorted(Comparator.comparing(Mod::modId)).toList(); List<Mod> mods = Arrays.stream(c.mods()).sorted(Comparator.comparing(Mod::modId)).toList();
Map<Mod, ModContainer> modMap = new HashMap<>(); Map<Mod, PlatformService.PlatformModContainer> modMap = new HashMap<>();
List<String> missingMods = new ArrayList<>(); List<String> missingMods = new ArrayList<>();
String modIds = String.join(", ", mods.stream().map(Mod::modId).toList()); String modIds = String.join(", ", mods.stream().map(Mod::modId).toList());
for (Mod m : mods) { for (Mod m : mods) {
String modId = m.modId(); String modId = m.modId();
Optional<ModContainer> opt = FabricLoader.getInstance().getModContainer(modId); Optional<PlatformService.PlatformModContainer> mod = PlatformService.getInstance().getMod(modId);
if (opt.isEmpty()) { if (mod.isEmpty()) {
missingMods.add(modId); missingMods.add(modId);
} else { } else {
modMap.put(m, opt.get()); modMap.put(m, mod.get());
} }
} }
if (!missingMods.isEmpty()) { if (!missingMods.isEmpty()) {
@@ -64,16 +77,14 @@ public class CompatMixinPlugin implements IMixinConfigPlugin {
logger.info("Skipped compatibility mixin with mod(s) [{}]: mod(s) {} are not present", modIds, missingMods); logger.info("Skipped compatibility mixin with mod(s) [{}]: mod(s) {} are not present", modIds, missingMods);
} }
} }
for (Map.Entry<Mod, ModContainer> e : modMap.entrySet()) { for (Map.Entry<Mod, PlatformService.PlatformModContainer> e : modMap.entrySet()) {
ModMetadata metadata = e.getValue().getMetadata(); for (String author : e.getKey().authors()) {
Set<String> authors = metadata.getAuthors().stream().map(Person::getName).collect(Collectors.toSet()); if (!e.getValue().hasAuthor(author)) return false;
if (!authors.containsAll(Arrays.asList(e.getKey().authors()))) {
return false;
} }
for (String v : e.getKey().versions()) { for (String v : e.getKey().versions()) {
if (v.isBlank()) continue; if (v.isBlank()) continue;
Version version = VersionParser.parse(v, true); ModVersion version = ModVersion.parse(v);
if (version.compareTo(metadata.getVersion()) != 0) return false; if (!e.getValue().matchesVersion(version)) return false;
} }
} }
if (loadedCompatModIds.add(modIds)) { if (loadedCompatModIds.add(modIds)) {
@@ -0,0 +1,9 @@
package com.nerjal.moddingutils.extra;
@SubjectiveOpinion({
"For the sake of leaving quirky comments in the code without affecting runtime",
"And it's funnier than just comments honestly"
})
public @interface SubjectiveOpinion {
String[] value();
}
@@ -0,0 +1,28 @@
package com.nerjal.moddingutils.mod;
import org.jetbrains.annotations.Contract;
/**
* This is straight-up copied from the Fabric loader api's versions,
* but since I did not find an easy way to only shadow the relevant
* classes, here we are.
*/
public interface ModVersion extends Comparable<ModVersion> {
String getFriendlyString();
@Contract("null -> fail")
static ModVersion parse(String string) throws VersionParsingException {
if (string == null || string.isEmpty()) {
throw new VersionParsingException("Version must be a non-empty string.");
}
ModVersion version;
try {
version = new SemanticModVersion(string);
} catch (VersionParsingException e) {
version = new StringModVersion(string);
}
return version;
}
}
@@ -0,0 +1,270 @@
package com.nerjal.moddingutils.mod;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
public class SemanticModVersion implements ModVersion {
private static final Pattern DOT_SEPARATED_ID = Pattern.compile("|[-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*");
private static final Pattern UNSIGNED_INTEGER = Pattern.compile("0|[1-9][0-9]*");
private static final int COMPONENT_WILDCARD = Integer.MIN_VALUE;
private final int[] components;
private final String prerelease;
private final String build;
private String friendlyName;
public SemanticModVersion(String version) throws VersionParsingException {
int buildDelimPos = version.indexOf('+');
if (buildDelimPos >= 0) {
build = version.substring(buildDelimPos + 1);
version = version.substring(0, buildDelimPos);
} else {
build = null;
}
int dashDelimPos = version.indexOf('-');
if (dashDelimPos >= 0) {
prerelease = version.substring(dashDelimPos + 1);
version = version.substring(0, dashDelimPos);
} else {
prerelease = null;
}
if (prerelease != null && !DOT_SEPARATED_ID.matcher(prerelease).matches()) {
throw new VersionParsingException("Invalid prerelease string '" + prerelease + "'!");
}
if (version.endsWith(".")) {
throw new VersionParsingException("Negative version number component found!");
} else if (version.startsWith(".")) {
throw new VersionParsingException("Missing version component!");
}
String[] componentStrings = version.split("\\.");
if (componentStrings.length < 1) {
throw new VersionParsingException("Did not provide version numbers!");
}
int[] components = new int[componentStrings.length];
int firstWildcardIdx = -1;
for (int i = 0; i < componentStrings.length; i++) {
String compStr = componentStrings[i];
if (compStr.equals("x") || compStr.equals("X") || compStr.equals("*")) {
if (prerelease != null) {
throw new VersionParsingException("Pre-release versions are not allowed to use X-ranges!");
}
components[i] = COMPONENT_WILDCARD;
if (firstWildcardIdx < 0) firstWildcardIdx = i;
continue;
} else if (i > 0 && components[i - 1] == COMPONENT_WILDCARD) {
throw new VersionParsingException("Interjacent wildcard (1.x.2) are disallowed!");
}
if (compStr.trim().isEmpty()) {
throw new VersionParsingException("Missing version number component!");
}
try {
components[i] = Integer.parseInt(compStr);
if (components[i] < 0) {
throw new VersionParsingException("Negative version number component '" + compStr + "'!");
}
} catch (NumberFormatException e) {
throw new VersionParsingException("Could not parse version number component '" + compStr + "'!", e);
}
}
if (components.length == 1 && components[0] == COMPONENT_WILDCARD) {
throw new VersionParsingException("Versions of form 'x' or 'X' not allowed!");
}
// strip extra wildcards (1.x.x -> 1.x)
if (firstWildcardIdx > 0 && components.length > firstWildcardIdx + 1) {
components = Arrays.copyOf(components, firstWildcardIdx + 1);
}
this.components = components;
buildFriendlyName();
}
public SemanticModVersion(int[] components, String prerelease, String build) {
if (components.length == 0 || components[0] == COMPONENT_WILDCARD) throw new IllegalArgumentException("Invalid components: "+Arrays.toString(components));
this.components = components;
this.prerelease = prerelease;
this.build = build;
buildFriendlyName();
}
private void buildFriendlyName() {
StringBuilder fnBuilder = new StringBuilder();
boolean first = true;
for (int i : components) {
if (first) {
first = false;
} else {
fnBuilder.append('.');
}
if (i == COMPONENT_WILDCARD) {
fnBuilder.append('x');
} else {
fnBuilder.append(i);
}
}
if (prerelease != null) {
fnBuilder.append('-').append(prerelease);
}
if (build != null) {
fnBuilder.append('+').append(build);
}
friendlyName = fnBuilder.toString();
}
public int getVersionComponentCount() {
return components.length;
}
public int getVersionComponent(int pos) {
if (pos < 0) {
throw new RuntimeException("Tried to access negative version number component!");
} else if (pos >= components.length) {
// Repeat "x" if x-range, otherwise repeat "0".
return components[components.length - 1] == COMPONENT_WILDCARD ? COMPONENT_WILDCARD : 0;
} else {
return components[pos];
}
}
public int[] getVersionComponents() {
return components.clone();
}
public Optional<String> getPrereleaseKey() {
return Optional.ofNullable(prerelease);
}
public Optional<String> getBuildKey() {
return Optional.ofNullable(build);
}
public String getFriendlyString() {
return friendlyName;
}
public boolean hasWildcard() {
for (int i : components) {
if (i < 0) {
return true;
}
}
return false;
}
public boolean equalsComponentsExactly(SemanticModVersion other) {
for (int i = 0; i < Math.max(getVersionComponentCount(), other.getVersionComponentCount()); i++) {
if (getVersionComponent(i) != other.getVersionComponent(i)) {
return false;
}
}
return true;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SemanticModVersion other)) return false;
if (!equalsComponentsExactly(other)) {
return false;
}
return Objects.equals(prerelease, other.prerelease) && Objects.equals(build, other.build);
}
@Override
public int hashCode() {
return Arrays.hashCode(components) * 73 + (prerelease != null ? prerelease.hashCode() * 11 : 0) + (build != null ? build.hashCode() : 0);
}
@Override
public String toString() {
return getFriendlyString();
}
@Override
public int compareTo(ModVersion other) {
if (!(other instanceof SemanticModVersion o)) {
return getFriendlyString().compareTo(other.getFriendlyString());
}
for (int i = 0; i < Math.max(getVersionComponentCount(), o.getVersionComponentCount()); i++) {
int first = getVersionComponent(i);
int second = o.getVersionComponent(i);
if (first == COMPONENT_WILDCARD || second == COMPONENT_WILDCARD) {
continue;
}
int compare = Integer.compare(first, second);
if (compare != 0) return compare;
}
Optional<String> prereleaseA = getPrereleaseKey();
Optional<String> prereleaseB = o.getPrereleaseKey();
if (prereleaseA.isPresent() || prereleaseB.isPresent()) {
if (prereleaseA.isPresent() && prereleaseB.isPresent()) {
StringTokenizer prereleaseATokenizer = new StringTokenizer(prereleaseA.get(), ".");
StringTokenizer prereleaseBTokenizer = new StringTokenizer(prereleaseB.get(), ".");
while (prereleaseATokenizer.hasMoreElements()) {
if (prereleaseBTokenizer.hasMoreElements()) {
String partA = prereleaseATokenizer.nextToken();
String partB = prereleaseBTokenizer.nextToken();
if (UNSIGNED_INTEGER.matcher(partA).matches()) {
if (UNSIGNED_INTEGER.matcher(partB).matches()) {
int compare = Integer.compare(partA.length(), partB.length());
if (compare != 0) return compare;
} else {
return -1;
}
} else {
if (UNSIGNED_INTEGER.matcher(partB).matches()) {
return 1;
}
}
int compare = partA.compareTo(partB);
if (compare != 0) return compare;
} else {
return 1;
}
}
return prereleaseBTokenizer.hasMoreElements() ? -1 : 0;
} else if (prereleaseA.isPresent()) {
return o.hasWildcard() ? 0 : -1;
} else { // prereleaseB.isPresent()
return hasWildcard() ? 0 : 1;
}
} else {
return 0;
}
}
}
@@ -0,0 +1,32 @@
package com.nerjal.moddingutils.mod;
public class StringModVersion implements ModVersion {
private final String version;
public StringModVersion(String version) {
this.version = version;
}
@Override
public String getFriendlyString() {
return version;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StringModVersion other)) {
return false;
}
return version.equals(other.version);
}
@Override
public int compareTo(ModVersion o) {
return getFriendlyString().compareTo(o.getFriendlyString());
}
@Override
public String toString() {
return version;
}
}
@@ -0,0 +1,19 @@
package com.nerjal.moddingutils.mod;
public class VersionParsingException extends Exception {
public VersionParsingException() {
super();
}
public VersionParsingException(Throwable t) {
super(t);
}
public VersionParsingException(String s) {
super(s);
}
public VersionParsingException(String s, Throwable t) {
super(s, t);
}
}
@@ -0,0 +1,39 @@
package com.nerjal.moddingutils.platform;
import com.nerjal.moddingutils.extra.SubjectiveOpinion;
public enum Loader {
FABRIC,
@Deprecated(since = "Since it has been led by a douche and a bunch of faggots")
@SubjectiveOpinion({
"The sole fact that this modloader is still alive baffles me.",
"Who still uses it? Who hasn't moved on? Why?"
})
FORGE,
@SubjectiveOpinion({
"I am not saying we should use forge instead, but I don't believe this loader is a good option.",
"Thank you for listening to my TED talk."
})
NEOFORGE,
@SubjectiveOpinion({
"I know you're gonna say I forgot one. I don't care. I will not add it.",
"I have no reason to endorse a dead project whose leaders have hurt my friends."
})
OTHER,
;
public static Loader parse(String s) {
return switch (s.toLowerCase()) {
case "modlauncher" -> {
if (Loader.class.getClassLoader().getResource("net/neoforged/fml/ModLoader.class") == null) {
yield FORGE;
} else {
yield NEOFORGE;
}
}
case "launchwrapper" -> FORGE;
case "knot/fabric", "launchwrapper/fabric" -> FABRIC;
default -> OTHER;
};
}
}
@@ -0,0 +1,88 @@
package com.nerjal.moddingutils.platform;
import com.nerjal.moddingutils.mod.ModVersion;
import com.nerjal.moddingutils.mod.VersionParsingException;
import org.jetbrains.annotations.Contract;
import org.spongepowered.asm.service.MixinService;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public abstract sealed class PlatformService permits PlatformServiceFabric, PlatformServiceForge, PlatformServiceNeoForge, PlatformServiceOther {
private static final Loader LOADER;
private static final Side SIDE;
private static PlatformService INSTANCE;
static {
String name = MixinService.getService().getName();
String side = MixinService.getService().getSideName();
LOADER = Loader.parse(name);
SIDE = Side.parse(side);
}
@Contract(pure = true)
public static Loader getLoader() {
return LOADER;
}
@Contract(pure = true)
public static Side getSide() {
return SIDE;
}
public static PlatformService getInstance() {
if (INSTANCE == null) {
INSTANCE = switch (LOADER) {
case FABRIC -> new PlatformServiceFabric();
case NEOFORGE -> new PlatformServiceNeoForge();
case FORGE -> new PlatformServiceForge();
default -> new PlatformServiceOther();
};
}
return INSTANCE;
}
private final Map<String, Optional<PlatformModContainer>> modsMap = new HashMap<>();
public final Optional<PlatformModContainer> getMod(String modId) {
return modsMap.computeIfAbsent(modId, this::createModContainer);
}
public abstract boolean isDevEnv();
@Contract(pure = true)
protected abstract Optional<PlatformModContainer> createModContainer(String modId);
public abstract static class PlatformModContainer {
private final String modId;
private final Set<String> authors;
private final ModVersion version;
protected PlatformModContainer(String modId, Set<String> authors, String versionString) {
this.modId = modId;
this.authors = authors;
try {
this.version = ModVersion.parse(versionString);
} catch (VersionParsingException e) {
throw new RuntimeException(e);
}
}
@Contract(pure = true)
public final boolean hasAuthor(String s) {
return authors.contains(s);
}
@Contract(pure = true)
public final boolean matchesVersion(ModVersion v) {
return version.compareTo(v) == 0;
}
@Contract(pure = true)
public final String getModId() {
return modId;
}
}
}
@@ -0,0 +1,32 @@
package com.nerjal.moddingutils.platform;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.api.metadata.Person;
import java.util.Optional;
import java.util.stream.Collectors;
final class PlatformServiceFabric extends PlatformService {
@Override
protected Optional<PlatformModContainer> createModContainer(String modId) {
return FabricLoader.getInstance().getModContainer(modId)
.map(ModContainer::getMetadata)
.map(FabricModContainer::new);
}
@Override
public boolean isDevEnv() {
return FabricLoader.getInstance().isDevelopmentEnvironment();
}
static final class FabricModContainer extends PlatformModContainer {
private FabricModContainer(ModMetadata modMetadata) {
super(modMetadata.getId(),
modMetadata.getAuthors().stream().map(Person::getName).collect(Collectors.toSet()),
modMetadata.getVersion().getFriendlyString());
}
}
}
@@ -0,0 +1,31 @@
package com.nerjal.moddingutils.platform;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.forgespi.language.IModFileInfo;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
final class PlatformServiceForge extends PlatformService {
@Override
protected Optional<PlatformModContainer> createModContainer(String modId) {
return Optional.ofNullable(ModList.get().getModFileById(modId)).map(i -> new ForgeModContainer(modId, i));
}
@Override
public boolean isDevEnv() {
return !FMLLoader.isProduction();
}
static final class ForgeModContainer extends PlatformModContainer {
ForgeModContainer(String modId, IModFileInfo modInfo) {
super(modId, Arrays.stream(modInfo.getConfig().getConfigElement("authors")
.map(Object::toString).orElse("").split("[\\w,+;]+"))
.collect(Collectors.toSet()),
modInfo.versionString());
}
}
}
@@ -0,0 +1,30 @@
package com.nerjal.moddingutils.platform;
import net.neoforged.fml.ModList;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforgespi.language.IModFileInfo;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
final class PlatformServiceNeoForge extends PlatformService {
@Override
protected Optional<PlatformModContainer> createModContainer(String modId) {
return Optional.ofNullable(ModList.get().getModFileById(modId)).map(i -> new NeoForgeModContainer(modId, i));
}
@Override
public boolean isDevEnv() {
return !FMLLoader.isProduction();
}
static final class NeoForgeModContainer extends PlatformModContainer {
NeoForgeModContainer(String modId, IModFileInfo modInfo) {
super(modId, Arrays.stream(modInfo.getConfig().getConfigElement("authors")
.map(Object::toString).orElse("").split("[\\w,+;]+"))
.collect(Collectors.toSet()),
modInfo.versionString());
}
}
}
@@ -0,0 +1,18 @@
package com.nerjal.moddingutils.platform;
import com.nerjal.moddingutils.extra.SubjectiveOpinion;
import java.util.Optional;
@SubjectiveOpinion("Unknown or voluntarily unsupported loaders")
final class PlatformServiceOther extends PlatformService {
@Override
protected Optional<PlatformModContainer> createModContainer(String modId) {
return Optional.empty();
}
@Override
public boolean isDevEnv() {
return false;
}
}
@@ -0,0 +1,16 @@
package com.nerjal.moddingutils.platform;
public enum Side {
CLIENT,
SERVER,
UNKNOWN,
;
public static Side parse(String s) {
return switch (s.toLowerCase()) {
case "client" -> CLIENT;
case "server" -> SERVER;
default -> UNKNOWN;
};
}
}
+16
View File
@@ -0,0 +1,16 @@
module ModdingUtils {
requires net.fabricmc.loader;
requires org.jetbrains.annotations;
requires org.objectweb.asm.tree;
requires org.slf4j;
requires org.spongepowered.mixin;
requires fmlcore;
requires net.minecraftforge.forgespi;
requires net.neoforged.neoforgespi;
requires fml_core;
requires fmlloader;
requires fml_loader;
opens com.nerjal.moddingutils.compat;
opens com.nerjal.moddingutils.mod;
opens com.nerjal.moddingutils.platform;
}
-35
View File
@@ -1,35 +0,0 @@
{
"schemaVersion": 1,
"id": "moddingutils",
"version": "${version}",
"name": "ModdingUtils",
"description": "Various modding utilities to share and use freely",
"authors": [
{
"name": "Nerjal Nosk",
"contact": {
"homepage": "https://codeberg.org/NerjalNosk"
}
}
],
"contact": {
"homepage": "https://codeberg.org/NerjalNosk/ModdingUtils"
},
"license": "LGPL-3.0",
"environment": "*",
"entrypoints": {
},
"mixins": [
],
"depends": {
"fabricloader": "*",
"java": "${java_version}"
},
"custom": {
"modmenu": {
"badges": [
"library"
]
}
}
}