diff --git a/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java b/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java index 0d2bf397..3cbeb4b9 100644 --- a/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java +++ b/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java @@ -13,4 +13,17 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) public @interface BloodMagicPlugin { + /** + * This annotation will inject the active {@link IBloodMagicAPI} into a {@code static} field of the same + * type. Fields with invalid types will be ignored and an error will be logged. + * + * These fields are populated during {@link net.minecraftforge.fml.common.event.FMLPreInitializationEvent}. + * + * {@code public static @BloodMagicPlugin.Inject IBloodMagicAPI API_INSTANCE = null;} + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Inject { + + } } diff --git a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java index 776bee6d..11ce4bce 100644 --- a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java +++ b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java @@ -6,6 +6,10 @@ import javax.annotation.Nonnull; /** * The main interface between a plugin and Blood Magic's internals. + * + * This API is intended for compatibility between other mods and Blood Magic. More advanced integration is out of the scope of this API and are considered "addons". + * + * To get an instance of this without actually creating an {@link IBloodMagicPlugin}, use {@link BloodMagicPlugin.Inject}. */ public interface IBloodMagicAPI { diff --git a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java index 78ac7036..c18d3c1d 100644 --- a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java +++ b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java @@ -7,9 +7,20 @@ package WayofTime.bloodmagic.api; public interface IBloodMagicPlugin { /** - * Register mod content with the API + * Register mod content with the API. Called during {@link net.minecraftforge.fml.common.event.FMLInitializationEvent}. * * @param api The active instance of the {@link IBloodMagicAPI} */ - void register(IBloodMagicAPI api); + default void register(IBloodMagicAPI api) { + // No-op + } + + /** + * Register recipes with the API. Called during {@link net.minecraftforge.event.RegistryEvent.Register}. + * + * @param recipeRegistrar The active instance of the {@link IBloodMagicRecipeRegistrar} + */ + default void registerRecipes(IBloodMagicRecipeRegistrar recipeRegistrar) { + // No-op + } } diff --git a/src/main/java/WayofTime/bloodmagic/BloodMagic.java b/src/main/java/WayofTime/bloodmagic/BloodMagic.java index 0b1122f4..ce2f8153 100644 --- a/src/main/java/WayofTime/bloodmagic/BloodMagic.java +++ b/src/main/java/WayofTime/bloodmagic/BloodMagic.java @@ -73,6 +73,7 @@ public class BloodMagic { configDir = new File(event.getModConfigurationDirectory(), "bloodmagic"); PLUGINS.addAll(PluginUtil.gatherPlugins(event.getAsmData())); + PluginUtil.injectAPIInstances(PluginUtil.gatherInjections(event.getAsmData())); ModTranquilityHandlers.init(); ModDungeons.init(); @@ -84,7 +85,7 @@ public class BloodMagic { public void init(FMLInitializationEvent event) { BloodMagicPacketHandler.init(); - PluginUtil.registerPlugins(PLUGINS); + PluginUtil.handlePluginStep(PluginUtil.RegistrationStep.PLUGIN_REGISTER); ModRecipes.init(); ModRituals.initRituals(); diff --git a/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java b/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java index 9f98f857..453d5174 100644 --- a/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java +++ b/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java @@ -6,6 +6,7 @@ import WayofTime.bloodmagic.api.BloodMagicPlugin; import WayofTime.bloodmagic.api.IBloodMagicAPI; import WayofTime.bloodmagic.api.IBloodMagicPlugin; import WayofTime.bloodmagic.altar.EnumAltarComponent; +import WayofTime.bloodmagic.api.IBloodMagicRecipeRegistrar; import WayofTime.bloodmagic.block.BlockBloodRune; import WayofTime.bloodmagic.block.BlockDecorative; import WayofTime.bloodmagic.block.enums.BloodRuneType; @@ -78,11 +79,14 @@ public class BloodMagicCorePlugin implements IBloodMagicPlugin { BlockBloodRune bloodRune = (BlockBloodRune) RegistrarBloodMagicBlocks.BLOOD_RUNE; for (BloodRuneType runeType : BloodRuneType.values()) api.registerAltarComponent(bloodRune.getDefaultState().withProperty(bloodRune.getProperty(), runeType), EnumAltarComponent.BLOODRUNE.name()); + } - RegistrarBloodMagicRecipes.registerAltarRecipes(api.getRecipeRegistrar()); - RegistrarBloodMagicRecipes.registerAlchemyTableRecipes(api.getRecipeRegistrar()); - RegistrarBloodMagicRecipes.registerTartaricForgeRecipes(api.getRecipeRegistrar()); - RegistrarBloodMagicRecipes.registerAlchemyArrayRecipes(api.getRecipeRegistrar()); + @Override + public void registerRecipes(IBloodMagicRecipeRegistrar recipeRegistrar) { + RegistrarBloodMagicRecipes.registerAltarRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); + RegistrarBloodMagicRecipes.registerAlchemyTableRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); + RegistrarBloodMagicRecipes.registerTartaricForgeRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); + RegistrarBloodMagicRecipes.registerAlchemyArrayRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); } private static void handleConfigValues(BloodMagicAPI api) { diff --git a/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java b/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java index 298b7480..85ceebe4 100644 --- a/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java +++ b/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java @@ -12,6 +12,7 @@ import WayofTime.bloodmagic.item.alchemy.ItemCuttingFluid; import WayofTime.bloodmagic.item.alchemy.ItemLivingArmourPointsUpgrade; import WayofTime.bloodmagic.item.soul.ItemSoulGem; import WayofTime.bloodmagic.item.types.ComponentTypes; +import WayofTime.bloodmagic.util.PluginUtil; import com.google.common.collect.Sets; import net.minecraft.init.Blocks; import net.minecraft.init.Items; @@ -51,6 +52,8 @@ public class RegistrarBloodMagicRecipes { OreDictionary.registerOre("dustIron", ComponentTypes.SAND_IRON.getStack()); OreDictionary.registerOre("dustGold", ComponentTypes.SAND_GOLD.getStack()); OreDictionary.registerOre("dustCoal", ComponentTypes.SAND_COAL.getStack()); + + PluginUtil.handlePluginStep(PluginUtil.RegistrationStep.RECIPE_REGISTER); } public static void registerAltarRecipes(BloodMagicRecipeRegistrar registrar) { diff --git a/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java b/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java index 2faf5d38..aeed6037 100644 --- a/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java +++ b/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java @@ -1,17 +1,23 @@ package WayofTime.bloodmagic.util; +import WayofTime.bloodmagic.BloodMagic; import WayofTime.bloodmagic.api.BloodMagicPlugin; +import WayofTime.bloodmagic.api.IBloodMagicAPI; import WayofTime.bloodmagic.api.IBloodMagicPlugin; import WayofTime.bloodmagic.api.impl.BloodMagicAPI; import WayofTime.bloodmagic.api.impl.BloodMagicCorePlugin; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; +import net.minecraftforge.common.util.EnumHelper; import net.minecraftforge.fml.common.discovery.ASMDataTable; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.List; import java.util.Set; +import java.util.function.Consumer; public class PluginUtil { @@ -20,7 +26,7 @@ public class PluginUtil { public static List> gatherPlugins(ASMDataTable dataTable) { Stopwatch stopwatch = Stopwatch.createStarted(); List> discoveredAnnotations = Lists.newArrayList(); - Set discoveredPlugins = dataTable.getAll(BloodMagicPlugin.class.getCanonicalName()); + Set discoveredPlugins = dataTable.getAll(BloodMagicPlugin.class.getName()); for (ASMDataTable.ASMData data : discoveredPlugins) { try { @@ -47,20 +53,85 @@ public class PluginUtil { return discoveredAnnotations; } - public static void registerPlugins(List> plugins) { - Stopwatch total = Stopwatch.createStarted(); - int errors = 0; - for (Pair plugin : plugins) { - Stopwatch per = Stopwatch.createStarted(); + @Nonnull + public static List gatherInjections(ASMDataTable dataTable) { + Stopwatch stopwatch = Stopwatch.createStarted(); + List injectees = Lists.newArrayList(); + Set discoveredInjectees = dataTable.getAll(BloodMagicPlugin.Inject.class.getName()); + + for (ASMDataTable.ASMData data : discoveredInjectees) { try { - plugin.getLeft().register(BloodMagicAPI.INSTANCE); + Class asmClass = Class.forName(data.getClassName()); + Field toInject = asmClass.getDeclaredField(data.getObjectName()); + if (toInject.getType() != IBloodMagicAPI.class) { + BMLog.API.error("Mod requested API injection on field {}.{} which is an invalid type.", data.getClassName(), data.getObjectName()); + continue; + } + + BMLog.API.info("Discovered injection request at {}.{}", data.getClassName(), data.getObjectName()); + injectees.add(toInject); } catch (Exception e) { - errors++; - BMLog.DEFAULT.error("Error loading plugin at {}: {}: {}", plugin.getLeft().getClass(), e.getClass().getSimpleName(), e.getMessage()); + e.printStackTrace(); } - BMLog.API.info("Registered plugin at {} in {}", plugin.getLeft().getClass(), per.stop()); } - BMLog.API.info("Registered {} plugins with {} errors in {}", plugins.size() - errors, errors, total.stop()); + BMLog.API.info("Discovered {} potential API injection(s) in {}", injectees.size(), stopwatch.stop()); + return injectees; + } + + public static void handlePluginStep(RegistrationStep step) { + Stopwatch total = Stopwatch.createStarted(); + int errors = 0; + for (Pair plugin : BloodMagic.PLUGINS) { + Stopwatch per = Stopwatch.createStarted(); + try { + step.getConsumer().accept(plugin); + } catch (Exception e) { + errors++; + BMLog.DEFAULT.error("Error handling plugin step {} at {}: {}: {}", step, plugin.getLeft().getClass(), e.getClass().getSimpleName(), e.getMessage()); + } + BMLog.API.info("Handled plugin step {} at {} in {}", step, plugin.getLeft().getClass(), per.stop()); + } + + BMLog.API.info("Handled {} plugin(s) at step {} with {} errors in {}", BloodMagic.PLUGINS.size() - errors, step, errors, total.stop()); + } + + public static void injectAPIInstances(List injectees) { + Stopwatch total = Stopwatch.createStarted(); + int errors = 0; + + for (Field injectee : injectees) { + Stopwatch per = Stopwatch.createStarted(); + if (!Modifier.isStatic(injectee.getModifiers())) + continue; + + try { + EnumHelper.setFailsafeFieldValue(injectee, null, BloodMagicAPI.INSTANCE); + } catch (Exception e) { + errors++; + BMLog.DEFAULT.error("Error injecting API instance at {}.{}", injectee.getDeclaringClass().getCanonicalName(), injectee.getName()); + } + BMLog.API.info("Injected API instance at {}.{} in {}", injectee.getDeclaringClass().getCanonicalName(), injectee.getName(), per.stop()); + } + + BMLog.API.info("Injected API {} times with {} errors in {}", injectees.size() - errors, errors, total.stop()); + } + + public enum RegistrationStep { + + PLUGIN_REGISTER(p -> p.getLeft().register(BloodMagicAPI.INSTANCE)), + RECIPE_REGISTER(p -> p.getLeft().registerRecipes(BloodMagicAPI.INSTANCE.getRecipeRegistrar())) + ; + + private final Consumer> consumer; + + RegistrationStep(Consumer> consumer) { + this.consumer = consumer; + } + + @Nonnull + public Consumer> getConsumer() { + return consumer; + } } }