add javadocs to api classes, build javadoc & sources jars, allow multiple mixin configs to be declared, format
This commit is contained in:
parent
4aec13abc2
commit
347ad2a58a
|
@ -18,6 +18,6 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./gradlew
|
chmod +x ./gradlew
|
||||||
./gradlew publishMavenJavaPublicationToFrogMCSnapshotsMavenRepository \
|
./gradlew :publishMavenJavaPublicationToFrogMCSnapshotsMavenRepository \
|
||||||
-PFrogMCSnapshotsMavenUsername=${{ secrets.MAVEN_PUSH_USER }} \
|
-PFrogMCSnapshotsMavenUsername=${{ secrets.MAVEN_PUSH_USER }} \
|
||||||
-PFrogMCSnapshotsMavenPassword=${{ secrets.MAVEN_PUSH_TOKEN }}
|
-PFrogMCSnapshotsMavenPassword=${{ secrets.MAVEN_PUSH_TOKEN }}
|
||||||
|
|
|
@ -38,6 +38,13 @@ dependencies {
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
|
||||||
|
withJavadocJar()
|
||||||
|
withSourcesJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.javadoc {
|
||||||
|
include("**/api/**")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
|
|
|
@ -10,17 +10,6 @@ credits = [
|
||||||
{ name = "You", roles = ["author", "other_role"] }
|
{ name = "You", roles = ["author", "other_role"] }
|
||||||
]
|
]
|
||||||
|
|
||||||
[frog.dependencies]
|
|
||||||
depends = [
|
|
||||||
{ id = "other_mod", name = "Other Mod", versions = ">=0.2.0 <0.5.2 || 0.1.1 || 1.x || 3 || ~5 || ^6.x", link = "https://example.com" }
|
|
||||||
]
|
|
||||||
breaks = [
|
|
||||||
{ id = "old_mod", versions = "*" }
|
|
||||||
]
|
|
||||||
provides = [
|
|
||||||
{ id = "provided_mod", version = "that.version.aa" }
|
|
||||||
]
|
|
||||||
|
|
||||||
[frog.extensions]
|
[frog.extensions]
|
||||||
pre_launch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension"
|
pre_launch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension"
|
||||||
mixin_config = "example_mod.mixins.json"
|
mixin_config = "example_mod.mixins.json"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package dev.frogmc.frogloader.api;
|
package dev.frogmc.frogloader.api;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import dev.frogmc.frogloader.api.env.Env;
|
import dev.frogmc.frogloader.api.env.Env;
|
||||||
|
@ -9,26 +9,92 @@ import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
import dev.frogmc.frogloader.api.plugin.FrogPlugin;
|
import dev.frogmc.frogloader.api.plugin.FrogPlugin;
|
||||||
import dev.frogmc.frogloader.impl.FrogLoaderImpl;
|
import dev.frogmc.frogloader.impl.FrogLoaderImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General API to interact with this loader.
|
||||||
|
*
|
||||||
|
* @see ModProperties
|
||||||
|
* @see FrogPlugin
|
||||||
|
* @see Env
|
||||||
|
*/
|
||||||
public interface FrogLoader {
|
public interface FrogLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of this loader.
|
||||||
|
*
|
||||||
|
* @return An instance of this loader
|
||||||
|
*/
|
||||||
static FrogLoader getInstance() {
|
static FrogLoader getInstance() {
|
||||||
return FrogLoaderImpl.getInstance();
|
return FrogLoaderImpl.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FrogPlugin> getPlugins();
|
/**
|
||||||
|
* Get all loaded plugins.
|
||||||
|
*
|
||||||
|
* @return A collection of all loaded plugins
|
||||||
|
*/
|
||||||
|
Collection<FrogPlugin> getPlugins();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current (physical) environment.
|
||||||
|
*
|
||||||
|
* @return The current environment
|
||||||
|
* @see Env
|
||||||
|
*/
|
||||||
Env getEnv();
|
Env getEnv();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current game directory.
|
||||||
|
*
|
||||||
|
* @return The current game directory
|
||||||
|
* <p>Note: Should always be the current working directory, but this method should</p>
|
||||||
|
* be preferred over using the working directory directly.
|
||||||
|
*/
|
||||||
Path getGameDir();
|
Path getGameDir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current config directory.
|
||||||
|
*
|
||||||
|
* @return The current config directory
|
||||||
|
*/
|
||||||
Path getConfigDir();
|
Path getConfigDir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current mods directory.
|
||||||
|
*
|
||||||
|
* @return The current mods directory
|
||||||
|
*/
|
||||||
Path getModsDir();
|
Path getModsDir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query whether this loader is currently running in a development environment.
|
||||||
|
*
|
||||||
|
* @return Whether this loader is currently running in a development environment
|
||||||
|
*/
|
||||||
boolean isDevelopment();
|
boolean isDevelopment();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query whether a specific mod is loaded.
|
||||||
|
*
|
||||||
|
* @param id The mod id to query for
|
||||||
|
* @return Whether a mod with this specific id is loaded
|
||||||
|
*/
|
||||||
boolean isModLoaded(String id);
|
boolean isModLoaded(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a mod's properties.
|
||||||
|
*
|
||||||
|
* @param id The mod id to query for
|
||||||
|
* @return An Optional containing the mod's properties, if it is present
|
||||||
|
* @see ModProperties
|
||||||
|
*/
|
||||||
Optional<ModProperties> getModProperties(String id);
|
Optional<ModProperties> getModProperties(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all loaded mods
|
||||||
|
*
|
||||||
|
* @return A collection of all loaded mods
|
||||||
|
* @see ModProperties
|
||||||
|
* @see FrogPlugin
|
||||||
|
*/
|
||||||
|
Collection<ModProperties> getMods();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,40 @@
|
||||||
package dev.frogmc.frogloader.api.env;
|
package dev.frogmc.frogloader.api.env;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
/**
|
||||||
|
* Physical environment constants
|
||||||
|
*/
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum Env {
|
public enum Env {
|
||||||
|
/**
|
||||||
|
* The physical client environment
|
||||||
|
*/
|
||||||
CLIENT("CLIENT", "client"),
|
CLIENT("CLIENT", "client"),
|
||||||
|
/**
|
||||||
|
* The physical (dedicated) server environment
|
||||||
|
*/
|
||||||
SERVER("SERVER", "server"),
|
SERVER("SERVER", "server"),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String mixinName, identifier;
|
|
||||||
|
private final String mixinName, identifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this environment's name, in the format Mixin understands.
|
||||||
|
*
|
||||||
|
* @return This environment's mixin name
|
||||||
|
*/
|
||||||
|
public String getMixinName() {
|
||||||
|
return this.mixinName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this environment's identifier
|
||||||
|
*
|
||||||
|
* @return This environment's identifier
|
||||||
|
*/
|
||||||
|
public String getIdentifier() {
|
||||||
|
return this.identifier;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
package dev.frogmc.frogloader.api.extensions;
|
package dev.frogmc.frogloader.api.extensions;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.mod.ModExtensions;
|
||||||
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
|
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Pre-Launch Extension.
|
* The Pre-Launch Extension.
|
||||||
* <p>This Extension is run right before the game is launched. (provided the used plugin supports it :) )</p>
|
* <p>This Extension is run right before the game is launched. (provided the used plugin supports it :) )</p>
|
||||||
|
*
|
||||||
|
* @see ModExtensions
|
||||||
*/
|
*/
|
||||||
public interface PreLaunchExtension {
|
public interface PreLaunchExtension {
|
||||||
|
/**
|
||||||
|
* This extension's id. This is the key to use in your frog.mod.toml.
|
||||||
|
*/
|
||||||
String ID = BuiltinExtensions.PRE_LAUNCH;
|
String ID = BuiltinExtensions.PRE_LAUNCH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initializer. This method will be invoked when this extension is run.
|
||||||
|
*/
|
||||||
void onPreLaunch();
|
void onPreLaunch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,36 @@ package dev.frogmc.frogloader.api.mod;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mod's dependencies.
|
||||||
|
*
|
||||||
|
* <p>Mod dependencies are declared in four types:</p>
|
||||||
|
* <code>Type.DEPEND</code>, <code>Type.BREAK</code>, <code>Type.SUGGEST</code> and <code>Type.PROVIDE</code>
|
||||||
|
*
|
||||||
|
* <p>Note: The <code>Type.PROVIDE</code> is a bit of a special case in the way that it only supports a single SemVer version instead of a full version range.</p>
|
||||||
|
*
|
||||||
|
* @see ModDependencies.Type
|
||||||
|
* @see ModDependencies.Entry
|
||||||
|
* @see ModProperties
|
||||||
|
* @see SemVer
|
||||||
|
*/
|
||||||
public final class ModDependencies {
|
public final class ModDependencies {
|
||||||
|
|
||||||
private final Map<Type, Collection<Entry>> entries = new HashMap<>();
|
private final Map<Type, Collection<Entry>> entries = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct new mod dependencies for a mod.
|
||||||
|
* <p><strong>Internal use only.</strong></p>
|
||||||
|
*
|
||||||
|
* @param depends <code>Type.DEPEND</code> entries
|
||||||
|
* @param breaks <code>Type.BREAK</code> entries
|
||||||
|
* @param suggests <code>Type.SUGGEST</code> entries
|
||||||
|
* @param provides <code>Type.PROVIDE</code> entries
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
public ModDependencies(Collection<Entry> depends, Collection<Entry> breaks, Collection<Entry> suggests, Collection<Entry> provides) {
|
public ModDependencies(Collection<Entry> depends, Collection<Entry> breaks, Collection<Entry> suggests, Collection<Entry> provides) {
|
||||||
entries.put(Type.DEPEND, depends);
|
entries.put(Type.DEPEND, depends);
|
||||||
entries.put(Type.BREAK, breaks);
|
entries.put(Type.BREAK, breaks);
|
||||||
|
@ -13,51 +39,120 @@ public final class ModDependencies {
|
||||||
entries.put(Type.PROVIDE, provides);
|
entries.put(Type.PROVIDE, provides);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dependencies of a specific mod for a specific type
|
||||||
|
*
|
||||||
|
* @param type the dependency type to query for
|
||||||
|
* @return a collection of dependency entries
|
||||||
|
*/
|
||||||
public Collection<Entry> getForType(Type type) {
|
public Collection<Entry> getForType(Type type) {
|
||||||
return entries.get(type);
|
return entries.get(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ModEntry> getForModId(String id) {
|
/**
|
||||||
|
* Get entries that depend on a specific mod's id
|
||||||
|
*
|
||||||
|
* @param id the mod id to find dependency entries for
|
||||||
|
* @return a collection of entries that depend on the given mod id
|
||||||
|
*/
|
||||||
|
public Collection<ModEntry> getForModId(String id) {
|
||||||
List<ModEntry> entries = new ArrayList<>();
|
List<ModEntry> entries = new ArrayList<>();
|
||||||
for (Type type : Type.values()) {
|
for (Type type : Type.values()) {
|
||||||
for (Entry entry : getForType(type)) {
|
for (Entry entry : getForType(type)) {
|
||||||
if (entry.id.equals(id)) {
|
if (entry.id.equals(id)) {
|
||||||
entries.add(new ModEntry(type, entry.range, entry.link));
|
entries.add(new ModEntry(type, entry.range, entry.link, entry.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency types to distinguish their variants.
|
||||||
|
*/
|
||||||
|
public enum Type {
|
||||||
|
/**
|
||||||
|
* Depend on another mod
|
||||||
|
*/
|
||||||
|
DEPEND,
|
||||||
|
/**
|
||||||
|
* Declare another mod as breaking with your own
|
||||||
|
*/
|
||||||
|
BREAK,
|
||||||
|
/**
|
||||||
|
* Suggest a user to install another mod
|
||||||
|
*/
|
||||||
|
SUGGEST,
|
||||||
|
/**
|
||||||
|
* Declare your mod to provide another mod
|
||||||
|
*/
|
||||||
|
PROVIDE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data class to handle entries depending on a specific mod id
|
||||||
|
*/
|
||||||
public static class ModEntry {
|
public static class ModEntry {
|
||||||
private final Type type;
|
private final Type type;
|
||||||
private final String range;
|
private final String range;
|
||||||
private final String link;
|
private final String link;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
private ModEntry(Type type, String range, String link) {
|
private ModEntry(Type type, String range, String link, String name) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.range = range;
|
this.range = range;
|
||||||
this.link = link;
|
this.link = link;
|
||||||
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this dependency's type
|
||||||
|
*
|
||||||
|
* @return This dependency's type
|
||||||
|
*/
|
||||||
public Type type() {
|
public Type type() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this dependency's version range
|
||||||
|
*
|
||||||
|
* @return This dependency's version range
|
||||||
|
*/
|
||||||
public String range() {
|
public String range() {
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String link(){
|
/**
|
||||||
|
* Get this dependency's link
|
||||||
|
* <p>May be null.</p>
|
||||||
|
*
|
||||||
|
* @return This dependency's link
|
||||||
|
*/
|
||||||
|
public @Nullable String link() {
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this dependency's (friendly) name
|
||||||
|
* <p>May be null.</p>
|
||||||
|
*
|
||||||
|
* @return This dependency's name
|
||||||
|
*/
|
||||||
|
public @Nullable String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General storage for dependencies
|
||||||
|
*
|
||||||
|
* @param id the mod's id
|
||||||
|
* @param range the dependency's version range
|
||||||
|
* @param link an optional link for information about this dependency
|
||||||
|
* @param name an optional (friendly) name for this dependency
|
||||||
|
*/
|
||||||
public record Entry(String id, String range, String link, String name) {
|
public record Entry(String id, String range, String link, String name) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
DEPEND, BREAK, SUGGEST, PROVIDE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,30 +9,66 @@ import java.util.function.Consumer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class stores a mod's extensions.
|
||||||
|
* <p>An extension is a simple key-value mapping of a string name to any value.</p>
|
||||||
|
* <p>This class further provides utility methods to easily work with extension values of various types,
|
||||||
|
* especially for extensions providing some form of class or method reference.</p>
|
||||||
|
*
|
||||||
|
* @see ModProperties
|
||||||
|
*/
|
||||||
public final class ModExtensions {
|
public final class ModExtensions {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModExtensions.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ModExtensions.class);
|
||||||
|
|
||||||
public static ModExtensions of(Map<String, Object> entries){
|
|
||||||
return new ModExtensions(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, Object> extensions;
|
private final Map<String, Object> extensions;
|
||||||
|
|
||||||
private ModExtensions(Map<String, Object> entries) {
|
private ModExtensions(Map<String, Object> entries) {
|
||||||
extensions = entries;
|
extensions = entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a new instance of this class.
|
||||||
|
* <p><strong>Internal use only.</strong></p>
|
||||||
|
*
|
||||||
|
* @param entries the entries read from the mod's properties file
|
||||||
|
* @return an instance of this class
|
||||||
|
*/
|
||||||
|
public static ModExtensions of(Map<String, Object> entries) {
|
||||||
|
return new ModExtensions(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the provided extension name.
|
||||||
|
*
|
||||||
|
* @param key The extension name to query
|
||||||
|
* @param <T> The type of the value of this extension
|
||||||
|
* @return The value of the extension, or null
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T get(String key) {
|
public <T> T get(String key) {
|
||||||
return (T) extensions.get(key);
|
return (T) extensions.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the provided extension name.
|
||||||
|
*
|
||||||
|
* @param key The extension name to query
|
||||||
|
* @param defaultValue a default value
|
||||||
|
* @param <T> The type of the value of this extension
|
||||||
|
* @return The value of the extension, or the default value if it isn't present
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getOrDefault(String key, T defaultValue) {
|
public <T> T getOrDefault(String key, T defaultValue) {
|
||||||
return (T) extensions.getOrDefault(key, defaultValue);
|
return (T) extensions.getOrDefault(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the given action on this extension if it is present.
|
||||||
|
*
|
||||||
|
* @param key the extension name to query
|
||||||
|
* @param action the action to run on the value of the extension if it is present
|
||||||
|
* @param <T> The type of the value of this extension
|
||||||
|
*/
|
||||||
public <T> void runIfPresent(String key, Consumer<T> action) {
|
public <T> void runIfPresent(String key, Consumer<T> action) {
|
||||||
T value = get(key);
|
T value = get(key);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
@ -40,6 +76,18 @@ public final class ModExtensions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the given action on this extension if it is present.
|
||||||
|
* <p>This method simplifies handling references to classes or methods.</p>
|
||||||
|
* It will first query the string value of the extension and, if it is found, create a new instance of the referenced class
|
||||||
|
* or invoke the referenced method to retrieve an instance of the provided class. Then the provided action is run on this
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* @param key The name of the extension
|
||||||
|
* @param type The class type of the extension (The class the extension class is extending/implementing)
|
||||||
|
* @param action The action to run on the newly retrieved instance of the provided class
|
||||||
|
* @param <T> The type of the class
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> void runIfPresent(String key, Class<T> type, Consumer<T> action) {
|
public <T> void runIfPresent(String key, Class<T> type, Consumer<T> action) {
|
||||||
String value = get(key);
|
String value = get(key);
|
||||||
|
|
|
@ -5,23 +5,75 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mod's properties. This class represents a mod at runtime.
|
||||||
|
* It is read from each mod's <code>frog.mod.toml</code> file.
|
||||||
|
*
|
||||||
|
* @see ModDependencies
|
||||||
|
* @see ModExtensions
|
||||||
|
* @see SemVer
|
||||||
|
*/
|
||||||
public interface ModProperties {
|
public interface ModProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this mod's id.
|
||||||
|
*
|
||||||
|
* @return The mod's id
|
||||||
|
*/
|
||||||
String id();
|
String id();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this mod's name.
|
||||||
|
*
|
||||||
|
* @return The mod's name
|
||||||
|
*/
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this mod's icon.
|
||||||
|
* <p>May be null if the mod doesn't specify an icon.</p>
|
||||||
|
*
|
||||||
|
* @return The mod's icon
|
||||||
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
String icon();
|
String icon();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this mod's version.
|
||||||
|
*
|
||||||
|
* @return The mod's version
|
||||||
|
*/
|
||||||
SemVer version();
|
SemVer version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this mod's license.
|
||||||
|
*
|
||||||
|
* @return The mod's license
|
||||||
|
*/
|
||||||
String license();
|
String license();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get credits for this mod.
|
||||||
|
* <p>This is a map containing people's names as keys and their roles in this mod as values.</p>
|
||||||
|
*
|
||||||
|
* @return The mod's credits.
|
||||||
|
*/
|
||||||
Map<String, Collection<String>> credits();
|
Map<String, Collection<String>> credits();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this mod's dependencies.
|
||||||
|
*
|
||||||
|
* @return The mod's dependencies
|
||||||
|
* @see ModDependencies
|
||||||
|
*/
|
||||||
ModDependencies dependencies();
|
ModDependencies dependencies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this mod's declared extensions.
|
||||||
|
*
|
||||||
|
* @return The mod's extensions
|
||||||
|
* @see ModExtensions
|
||||||
|
*/
|
||||||
ModExtensions extensions();
|
ModExtensions extensions();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,46 @@
|
||||||
package dev.frogmc.frogloader.api.mod;
|
package dev.frogmc.frogloader.api.mod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple access to SemVer-style versions. This is used for versioning mods.
|
||||||
|
*
|
||||||
|
* @see ModProperties
|
||||||
|
*/
|
||||||
public interface SemVer extends Comparable<SemVer> {
|
public interface SemVer extends Comparable<SemVer> {
|
||||||
|
/**
|
||||||
|
* Get this version's major version component.
|
||||||
|
*
|
||||||
|
* @return This version's major version component
|
||||||
|
*/
|
||||||
int major();
|
int major();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this version's minor version component.
|
||||||
|
*
|
||||||
|
* @return This version's minor version component
|
||||||
|
*/
|
||||||
int minor();
|
int minor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this version's patch version component.
|
||||||
|
*
|
||||||
|
* @return This version's patch version component
|
||||||
|
*/
|
||||||
int patch();
|
int patch();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this version's pre-release version component.
|
||||||
|
*
|
||||||
|
* @return This version's pre-release version component
|
||||||
|
*/
|
||||||
String prerelease();
|
String prerelease();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this version's build version component.
|
||||||
|
*
|
||||||
|
* @return This version's build version component
|
||||||
|
*/
|
||||||
String build();
|
String build();
|
||||||
|
|
||||||
|
@Override
|
||||||
boolean equals(Object other);
|
boolean equals(Object other);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,22 +6,51 @@ import java.util.Collections;
|
||||||
import dev.frogmc.frogloader.api.FrogLoader;
|
import dev.frogmc.frogloader.api.FrogLoader;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Plugin that may load mods for a specific game and environment
|
||||||
|
*
|
||||||
|
* @see FrogLoader
|
||||||
|
*/
|
||||||
public interface FrogPlugin {
|
public interface FrogPlugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General run method of this plugin. This method is run after all plugins have been initialized.
|
||||||
|
*
|
||||||
|
* @see FrogPlugin#init(FrogLoader)
|
||||||
|
* @see FrogPlugin#getMods()
|
||||||
|
*/
|
||||||
default void run() {
|
default void run() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Check whether this plugin is applicable for the current environment
|
||||||
|
*
|
||||||
* @return Whether this plugin is applicable to be loaded in the current environment
|
* @return Whether this plugin is applicable to be loaded in the current environment
|
||||||
*/
|
*/
|
||||||
default boolean isApplicable() {
|
default boolean isApplicable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialization method for this plugin. This method will be called after <code>isApplicable()</code>
|
||||||
|
* if it returns true to initialize is plugin.
|
||||||
|
*
|
||||||
|
* @param loader the loader loading this plugin
|
||||||
|
* @throws Exception This method may throw any exception, it will be handled by the loader.
|
||||||
|
* @see FrogPlugin#isApplicable()
|
||||||
|
* @see FrogPlugin#getMods()
|
||||||
|
*/
|
||||||
default void init(FrogLoader loader) throws Exception {
|
default void init(FrogLoader loader) throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should return all mods loaded by this plugin. It will be queried after <code>init(FrogLoader)</code>
|
||||||
|
* and before <code>run()</code>.
|
||||||
|
*
|
||||||
|
* @return A collection of mods loaded by this plugin.
|
||||||
|
* @see FrogPlugin#init(FrogLoader)
|
||||||
|
* @see FrogPlugin#run()
|
||||||
|
*/
|
||||||
default Collection<ModProperties> getMods() {
|
default Collection<ModProperties> getMods() {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,9 @@ import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||||
|
|
||||||
public class FrogLoaderImpl implements FrogLoader {
|
public class FrogLoaderImpl implements FrogLoader {
|
||||||
public static final String MOD_FILE_EXTENSION = ".frogmod";
|
public static final String MOD_FILE_EXTENSION = ".frogmod";
|
||||||
|
private static final boolean DEV_ENV = Boolean.getBoolean(SystemProperties.DEVELOPMENT);
|
||||||
@Getter
|
@Getter
|
||||||
private static FrogLoaderImpl instance;
|
private static FrogLoaderImpl instance;
|
||||||
private final boolean DEV_ENV = Boolean.getBoolean(SystemProperties.DEVELOPMENT);
|
|
||||||
@Getter
|
@Getter
|
||||||
private final String[] args;
|
private final String[] args;
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -164,6 +164,7 @@ public class FrogLoaderImpl implements FrogLoader {
|
||||||
return mods.keySet();
|
return mods.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Collection<ModProperties> getMods() {
|
public Collection<ModProperties> getMods() {
|
||||||
return mods.values();
|
return mods.values();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,43 +26,13 @@ import org.jetbrains.annotations.Nullable;
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class LoaderGui {
|
public class LoaderGui {
|
||||||
|
|
||||||
|
private final JFrame frame;
|
||||||
|
private final boolean keepRunning;
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
private Path report;
|
|
||||||
private Consumer<JFrame> contentFunc;
|
|
||||||
private boolean keepRunning = false;
|
|
||||||
|
|
||||||
public <T> Builder setContent(ContentType<T> type, T argument){
|
|
||||||
contentFunc = f -> type.contentSetter.accept(f, argument);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addReport(Path report){
|
|
||||||
this.report = report;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder keepRunning(){
|
|
||||||
keepRunning = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoaderGui build(){
|
|
||||||
JFrame frame = getFrame(report);
|
|
||||||
if (contentFunc != null) {
|
|
||||||
contentFunc.accept(frame);
|
|
||||||
}
|
|
||||||
return new LoaderGui(frame, keepRunning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final JFrame frame;
|
|
||||||
private final boolean keepRunning;
|
|
||||||
|
|
||||||
private static JFrame getFrame(Path report) {
|
private static JFrame getFrame(Path report) {
|
||||||
var frame = new JFrame();
|
var frame = new JFrame();
|
||||||
frame.setTitle("FrogLoader " + LoaderGui.class.getPackage().getImplementationVersion());
|
frame.setTitle("FrogLoader " + LoaderGui.class.getPackage().getImplementationVersion());
|
||||||
|
@ -103,6 +73,49 @@ public class LoaderGui {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String printVersionRange(ModDependencyResolver.VersionRange range) {
|
||||||
|
return "range: \n" + range.toString().replace("||", "or");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JPanel getEntry(String description, ModDependencyResolver.VersionRange range, Color background, @Nullable String icon, List<JButton> actions) {
|
||||||
|
JPanel entry = new JPanel(new BorderLayout());
|
||||||
|
entry.setBorder(BasicBorders.getInternalFrameBorder());
|
||||||
|
Box text = Box.createVerticalBox();
|
||||||
|
text.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
JTextPane desc = new JTextPane();
|
||||||
|
desc.setContentType("text/html");
|
||||||
|
desc.setEditable(false);
|
||||||
|
desc.setBackground(background);
|
||||||
|
desc.setText("<html>" + description.replace("<", "<").replace("\n", "<br>") + "</html>");
|
||||||
|
JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
actions.forEach(actionPanel::add);
|
||||||
|
if (icon != null) {
|
||||||
|
URL location = LoaderGui.class.getResource(icon);
|
||||||
|
if (location != null) {
|
||||||
|
entry.add(new JLabel(new ImageIcon(location)), BorderLayout.WEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text.add(desc);
|
||||||
|
JCheckBox showAdvanced = new JCheckBox("Show raw version range");
|
||||||
|
JTextPane advanced = new JTextPane();
|
||||||
|
advanced.setEditable(false);
|
||||||
|
advanced.setBackground(background);
|
||||||
|
advanced.setText(range.toString());
|
||||||
|
advanced.setVisible(false);
|
||||||
|
text.add(advanced);
|
||||||
|
showAdvanced.addActionListener(new AbstractAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
advanced.setVisible(showAdvanced.isSelected());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
entry.add(text);
|
||||||
|
Box bottom = Box.createHorizontalBox();
|
||||||
|
bottom.add(showAdvanced, actionPanel);
|
||||||
|
entry.add(bottom, BorderLayout.SOUTH);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("BusyWait")
|
@SuppressWarnings("BusyWait")
|
||||||
public void show() {
|
public void show() {
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
|
@ -119,6 +132,36 @@ public class LoaderGui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private Path report;
|
||||||
|
private Consumer<JFrame> contentFunc;
|
||||||
|
private boolean keepRunning = false;
|
||||||
|
|
||||||
|
public <T> Builder setContent(ContentType<T> type, T argument) {
|
||||||
|
contentFunc = f -> type.contentSetter.accept(f, argument);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addReport(Path report) {
|
||||||
|
this.report = report;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder keepRunning() {
|
||||||
|
keepRunning = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoaderGui build() {
|
||||||
|
JFrame frame = getFrame(report);
|
||||||
|
if (contentFunc != null) {
|
||||||
|
contentFunc.accept(frame);
|
||||||
|
}
|
||||||
|
return new LoaderGui(frame, keepRunning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class ContentType<T> {
|
public static class ContentType<T> {
|
||||||
public static final ContentType<ModDependencyResolver.UnfulfilledDependencyException> INFO_UNFULFILLED_DEP = new ContentType<>((frame, ex) -> {
|
public static final ContentType<ModDependencyResolver.UnfulfilledDependencyException> INFO_UNFULFILLED_DEP = new ContentType<>((frame, ex) -> {
|
||||||
|
@ -235,47 +278,4 @@ public class LoaderGui {
|
||||||
|
|
||||||
private final BiConsumer<JFrame, T> contentSetter;
|
private final BiConsumer<JFrame, T> contentSetter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String printVersionRange(ModDependencyResolver.VersionRange range){
|
|
||||||
return "range: \n"+ range.toString().replace("||", "or");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JPanel getEntry(String description, ModDependencyResolver.VersionRange range, Color background, @Nullable String icon, List<JButton> actions){
|
|
||||||
JPanel entry = new JPanel(new BorderLayout());
|
|
||||||
entry.setBorder(BasicBorders.getInternalFrameBorder());
|
|
||||||
Box text = Box.createVerticalBox();
|
|
||||||
text.setBorder(BorderFactory.createEmptyBorder());
|
|
||||||
JTextPane desc = new JTextPane();
|
|
||||||
desc.setContentType("text/html");
|
|
||||||
desc.setEditable(false);
|
|
||||||
desc.setBackground(background);
|
|
||||||
desc.setText("<html>" + description.replace("<", "<").replace("\n", "<br>") + "</html>");
|
|
||||||
JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
|
||||||
actions.forEach(actionPanel::add);
|
|
||||||
if (icon != null) {
|
|
||||||
URL location = LoaderGui.class.getResource(icon);
|
|
||||||
if (location != null) {
|
|
||||||
entry.add(new JLabel(new ImageIcon(location)), BorderLayout.WEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text.add(desc);
|
|
||||||
JCheckBox showAdvanced = new JCheckBox("Show raw version range");
|
|
||||||
JTextPane advanced = new JTextPane();
|
|
||||||
advanced.setEditable(false);
|
|
||||||
advanced.setBackground(background);
|
|
||||||
advanced.setText(range.toString());
|
|
||||||
advanced.setVisible(false);
|
|
||||||
text.add(advanced);
|
|
||||||
showAdvanced.addActionListener(new AbstractAction() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
advanced.setVisible(showAdvanced.isSelected());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
entry.add(text);
|
|
||||||
Box bottom = Box.createHorizontalBox();
|
|
||||||
bottom.add(showAdvanced, actionPanel);
|
|
||||||
entry.add(bottom, BorderLayout.SOUTH);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ public class AccessWidener {
|
||||||
get().loadFromData(data);
|
get().loadFromData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] processClass(byte[] classBytes, String className) {
|
||||||
|
return get().process(classBytes, className);
|
||||||
|
}
|
||||||
|
|
||||||
private void loadFromData(Data data) {
|
private void loadFromData(Data data) {
|
||||||
classMap.putAll(data.classMap);
|
classMap.putAll(data.classMap);
|
||||||
methods.putAll(data.methods);
|
methods.putAll(data.methods);
|
||||||
|
@ -34,10 +38,6 @@ public class AccessWidener {
|
||||||
classNames.addAll(data.classNames);
|
classNames.addAll(data.classNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] processClass(byte[] classBytes, String className) {
|
|
||||||
return get().process(classBytes, className);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] process(byte[] classBytes, String className) {
|
private byte[] process(byte[] classBytes, String className) {
|
||||||
if (!classNames.contains(className)) {
|
if (!classNames.contains(className)) {
|
||||||
return classBytes;
|
return classBytes;
|
||||||
|
@ -97,23 +97,6 @@ public class AccessWidener {
|
||||||
return writer.toByteArray();
|
return writer.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Data(Map<String, Entry> classMap,
|
|
||||||
Map<String, Map<String, Entry>> methods,
|
|
||||||
Map<String, Map<String, Entry>> fields,
|
|
||||||
Map<String, Map<String, Entry>> mutations,
|
|
||||||
Set<String> classNames) {}
|
|
||||||
|
|
||||||
public record Entry(AccessType type, String targetType, String className, String name, String descriptor) {
|
|
||||||
|
|
||||||
public Entry(String[] line) {
|
|
||||||
this(AccessType.of(line[0]), line[1], line[2], line[3], line[4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAccessGreaterThan(Entry other) {
|
|
||||||
return Access.of(type().access).index < Access.of(other.type.access).index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum AccessType {
|
public enum AccessType {
|
||||||
ACCESSIBLE("accessible", Opcodes.ACC_PUBLIC),
|
ACCESSIBLE("accessible", Opcodes.ACC_PUBLIC),
|
||||||
|
@ -146,4 +129,22 @@ public class AccessWidener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record Data(Map<String, Entry> classMap,
|
||||||
|
Map<String, Map<String, Entry>> methods,
|
||||||
|
Map<String, Map<String, Entry>> fields,
|
||||||
|
Map<String, Map<String, Entry>> mutations,
|
||||||
|
Set<String> classNames) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Entry(AccessType type, String targetType, String className, String name, String descriptor) {
|
||||||
|
|
||||||
|
public Entry(String[] line) {
|
||||||
|
this(AccessType.of(line[0]), line[1], line[2], line[3], line[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccessGreaterThan(Entry other) {
|
||||||
|
return Access.of(type().access).index < Access.of(other.type.access).index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,13 @@ import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||||
public class MixinClassLoader extends URLClassLoader {
|
public class MixinClassLoader extends URLClassLoader {
|
||||||
|
|
||||||
private static final ClassLoader SYSTEM = ClassLoader.getSystemClassLoader();
|
private static final ClassLoader SYSTEM = ClassLoader.getSystemClassLoader();
|
||||||
private final List<String> exclusions = new ArrayList<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
registerAsParallelCapable();
|
registerAsParallelCapable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final List<String> exclusions = new ArrayList<>();
|
||||||
|
|
||||||
public MixinClassLoader() {
|
public MixinClassLoader() {
|
||||||
super(new URL[0], null);
|
super(new URL[0], null);
|
||||||
excludePackage("java");
|
excludePackage("java");
|
||||||
|
|
|
@ -139,9 +139,6 @@ public class ModDependencyResolver {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ProvidedMod(String modId, SemVer version, ModProperties source) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
private enum DependencyType {
|
private enum DependencyType {
|
||||||
EQ("=", Object::equals),
|
EQ("=", Object::equals),
|
||||||
|
@ -163,6 +160,8 @@ public class ModDependencyResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record ProvidedMod(String modId, SemVer version, ModProperties source) {
|
||||||
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static class BreakingModException extends Exception {
|
public static class BreakingModException extends Exception {
|
||||||
|
|
|
@ -51,7 +51,7 @@ public record SemVerImpl(int major, int minor, int patch, String prerelease, Str
|
||||||
if (obj instanceof SemVerImpl s) {
|
if (obj instanceof SemVerImpl s) {
|
||||||
return compareTo(s) == 0;
|
return compareTo(s) == 0;
|
||||||
}
|
}
|
||||||
return false;
|
return toString().equals(obj.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -48,6 +48,7 @@ public class Minecraft implements FrogPlugin {
|
||||||
return gamePath != null;
|
return gamePath != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
@Override
|
@Override
|
||||||
public void init(FrogLoader loader) throws Exception {
|
public void init(FrogLoader loader) throws Exception {
|
||||||
if (gamePath == null) {
|
if (gamePath == null) {
|
||||||
|
@ -98,9 +99,11 @@ public class Minecraft implements FrogPlugin {
|
||||||
}).forEach(FrogLoaderImpl.getInstance().getClassloader()::addURL);
|
}).forEach(FrogLoaderImpl.getInstance().getClassloader()::addURL);
|
||||||
|
|
||||||
modProperties.forEach(props -> {
|
modProperties.forEach(props -> {
|
||||||
String name = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG);
|
Object o = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG);
|
||||||
if (name != null) {
|
if (o instanceof String name) {
|
||||||
Mixins.addConfiguration(name);
|
Mixins.addConfiguration(name);
|
||||||
|
} else if (o instanceof Collection l) {
|
||||||
|
((Collection<String>) l).forEach(Mixins::addConfiguration);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.*;
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue