From db1a194e1f3b1c8eeae75a06837f1d05859cbade Mon Sep 17 00:00:00 2001 From: TT432 <2437315224@qq.com> Date: Sat, 14 Sep 2024 22:59:53 +0800 Subject: [PATCH] :sparkles: rpc --- README.md | 21 ++- build.gradle | 15 +- gradle.properties | 2 +- src/main/java/io/github/tt432/chin/Chin.java | 6 + .../io/github/tt432/chin/rpc/api/Rpc.java | 47 +++++++ .../tt432/chin/rpc/api/RpcSourceRegister.java | 51 +++++++ .../tt432/chin/rpc/api/RpcSourceType.java | 25 ++++ .../github/tt432/chin/rpc/impl/RpcAspect.java | 41 ++++++ .../tt432/chin/rpc/impl/RpcManager.java | 132 ++++++++++++++++++ .../chin/rpc/impl/RpcMethodIdMapPacket.java | 36 +++++ .../chin/rpc/impl/RpcNetworkHandler.java | 52 +++++++ .../tt432/chin/rpc/impl/RpcNetworkPacket.java | 51 +++++++ .../github/tt432/chin/util/ChinNetworks.java | 31 ++++ .../resources/META-INF/neoforge.mods.toml | 4 +- src/main/resources/chin.mixins.json | 14 ++ 15 files changed, 523 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/github/tt432/chin/rpc/api/Rpc.java create mode 100644 src/main/java/io/github/tt432/chin/rpc/api/RpcSourceRegister.java create mode 100644 src/main/java/io/github/tt432/chin/rpc/api/RpcSourceType.java create mode 100644 src/main/java/io/github/tt432/chin/rpc/impl/RpcAspect.java create mode 100644 src/main/java/io/github/tt432/chin/rpc/impl/RpcManager.java create mode 100644 src/main/java/io/github/tt432/chin/rpc/impl/RpcMethodIdMapPacket.java create mode 100644 src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkHandler.java create mode 100644 src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkPacket.java create mode 100644 src/main/java/io/github/tt432/chin/util/ChinNetworks.java create mode 100644 src/main/resources/chin.mixins.json diff --git a/README.md b/README.md index 1fff8c5..1d3071b 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,23 @@ Tuple - a heterogeneous list TupleCodec - Codec for Tuple -ChinExtraCodecs - util class for Codec \ No newline at end of file +ChinExtraCodecs - util class for Codec + +### Rpc + +Chin's rpc using [AspectJ](https://eclipse.dev/aspectj/). + +build.gradle: + +```groovy +plugins { + // ... + id "io.freefair.aspectj.post-compile-weaving" version aspectj_gradle_plugin_version +} + +dependencies { + // ... + implementation("org.aspectj:aspectjrt:{aspectj_version}") + aspect implementation("io.github.tt432:chin:{chin_version}") +} +``` \ No newline at end of file diff --git a/build.gradle b/build.gradle index ba235a4..192f435 100644 --- a/build.gradle +++ b/build.gradle @@ -3,8 +3,10 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.gradle.userdev' version '7.0.145' + id 'net.neoforged.gradle.userdev' version '7.0.163' id 'signing' + id "io.freefair.lombok" version "8.6" + id "io.freefair.aspectj.post-compile-weaving" version "8.10" } tasks.named('wrapper', Wrapper).configure { @@ -33,6 +35,11 @@ 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 +configurations { + libraries {} + implementation.extendsFrom libraries +} + // Default run configurations. // These can be tweaked, removed, or duplicated as needed. runs { @@ -51,6 +58,10 @@ runs { systemProperty 'forge.logging.console.level', 'debug' modSource project.sourceSets.main + + dependencies { + runtime project.configurations.libraries + } } client { @@ -114,6 +125,8 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter:5.10.3" testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + jarJar(libraries("org.aspectj:aspectjrt:1.9.21.1")) } // This block of code expands all declared replace properties in the specified resource targets. diff --git a/gradle.properties b/gradle.properties index 7d4f38f..154437c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,7 +32,7 @@ mod_name=Chin # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=1.0.1 +mod_version=21.0.1 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/io/github/tt432/chin/Chin.java b/src/main/java/io/github/tt432/chin/Chin.java index e64efb1..1d903f7 100644 --- a/src/main/java/io/github/tt432/chin/Chin.java +++ b/src/main/java/io/github/tt432/chin/Chin.java @@ -1,5 +1,7 @@ package io.github.tt432.chin; +import io.github.tt432.chin.rpc.api.RpcSourceRegister; +import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; /** @@ -8,4 +10,8 @@ @Mod(Chin.MOD_ID) public class Chin { public static final String MOD_ID = "chin"; + + public Chin(IEventBus bus) { + RpcSourceRegister.CHIN_DEFERRED_REGISTER.register(bus); + } } diff --git a/src/main/java/io/github/tt432/chin/rpc/api/Rpc.java b/src/main/java/io/github/tt432/chin/rpc/api/Rpc.java new file mode 100644 index 0000000..c847634 --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/api/Rpc.java @@ -0,0 +1,47 @@ +package io.github.tt432.chin.rpc.api; + +import io.github.tt432.chin.rpc.impl.RpcAspect; +import net.neoforged.api.distmarker.Dist; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * example: + *
{@code
+ * class MyBlockEntity extends BlockEntity {
+ *     @Rpc(CLIENT)
+ *     public void clickClick() {
+ *         // do something
+ *     }
+ * }
+ * }
+ * + * the method will send RpcNetworkPacket to server. equals: + * + *
{@code
+ * class MyBlockEntity extends BlockEntity {
+ *     public void clickClick() {
+ *         if (isClientSide()) {
+ *             // send packet
+ *         } else {
+ *             // do something
+ *         }
+ *     }
+ * }
+ * }
+ * + * @author TT432 + * @see RpcAspect + * @see RpcSourceRegister + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Rpc { + /** + * @return sender side + */ + Dist value(); +} diff --git a/src/main/java/io/github/tt432/chin/rpc/api/RpcSourceRegister.java b/src/main/java/io/github/tt432/chin/rpc/api/RpcSourceRegister.java new file mode 100644 index 0000000..672bfce --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/api/RpcSourceRegister.java @@ -0,0 +1,51 @@ +package io.github.tt432.chin.rpc.api; + +import io.github.tt432.chin.Chin; +import io.github.tt432.chin.util.ChinNetworks; +import lombok.experimental.UtilityClass; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.NewRegistryEvent; +import net.neoforged.neoforge.registries.RegistryBuilder; + +/** + * @author TT432 + */ +@UtilityClass +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) +public class RpcSourceRegister { + public final ResourceKey>> RPC_SOURCE_TYPE_KEY = + ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(Chin.MOD_ID, "rpc_source_type")); + public final Registry> REGISTRY = new RegistryBuilder<>(RPC_SOURCE_TYPE_KEY).sync(true).create(); + + public final DeferredRegister> CHIN_DEFERRED_REGISTER = DeferredRegister.create(RPC_SOURCE_TYPE_KEY, Chin.MOD_ID); + + public final DeferredHolder, RpcSourceType> BLOCK_ENTITY = + CHIN_DEFERRED_REGISTER.register("block_entity", () -> new RpcSourceType<>( + BlockEntity.class, + be -> be.getBlockPos().asLong(), + (ctx, id) -> { + BlockPos pos = BlockPos.of(id); + Level level = ChinNetworks.getLevel(ctx); + + if (level.isLoaded(pos)) { + return level.getBlockEntity(pos); + } + + return null; + } + )); + + @SubscribeEvent + public void onEvent(NewRegistryEvent event) { + event.register(REGISTRY); + } +} diff --git a/src/main/java/io/github/tt432/chin/rpc/api/RpcSourceType.java b/src/main/java/io/github/tt432/chin/rpc/api/RpcSourceType.java new file mode 100644 index 0000000..8d91983 --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/api/RpcSourceType.java @@ -0,0 +1,25 @@ +package io.github.tt432.chin.rpc.api; + +import net.neoforged.neoforge.network.handling.IPayloadContext; + +import javax.annotation.Nullable; + +/** + * @author TT432 + */ +public record RpcSourceType( + Class clazz, + IdGetter idGetter, + ObjectGetter objectGetter +) { + @FunctionalInterface + public interface IdGetter { + long getId(T object); + } + + @FunctionalInterface + public interface ObjectGetter { + @Nullable + T getObject(IPayloadContext context, long id); + } +} diff --git a/src/main/java/io/github/tt432/chin/rpc/impl/RpcAspect.java b/src/main/java/io/github/tt432/chin/rpc/impl/RpcAspect.java new file mode 100644 index 0000000..c96b9ca --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/impl/RpcAspect.java @@ -0,0 +1,41 @@ +package io.github.tt432.chin.rpc.impl; + +import io.github.tt432.chin.rpc.api.Rpc; +import io.github.tt432.chin.util.ChinNetworks; +import net.neoforged.neoforge.network.PacketDistributor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +/** + * @author TT432 + */ +@Aspect +public class RpcAspect { + @Pointcut("@annotation(rpc) && args() && target(target)") + public void rpcPointcut(Rpc rpc, Object target) { + } + + @Around("rpcPointcut(rpc, target)") + public Object processRpc(Rpc rpc, Object target, ProceedingJoinPoint joinPoint) throws Throwable { + if (rpc.value().isClient() == ChinNetworks.isClientSide()) { + RpcNetworkPacket packet = RpcNetworkPacket.of( + target, + target.getClass().getName() + "#" + joinPoint.getSignature().getName() + ); + + if (packet != null) { + if (rpc.value().isClient()) { + PacketDistributor.sendToServer(packet); + } else { + PacketDistributor.sendToAllPlayers(packet); + } + } + + return null; + } else { + return joinPoint.proceed(); + } + } +} diff --git a/src/main/java/io/github/tt432/chin/rpc/impl/RpcManager.java b/src/main/java/io/github/tt432/chin/rpc/impl/RpcManager.java new file mode 100644 index 0000000..aa0d2c1 --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/impl/RpcManager.java @@ -0,0 +1,132 @@ +package io.github.tt432.chin.rpc.impl; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import io.github.tt432.chin.rpc.api.Rpc; +import io.github.tt432.chin.rpc.api.RpcSourceRegister; +import io.github.tt432.chin.rpc.api.RpcSourceType; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import lombok.experimental.UtilityClass; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModList; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.neoforgespi.language.ModFileScanData; + +import javax.annotation.Nullable; +import java.lang.annotation.ElementType; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * @author TT432 + */ +@UtilityClass +public class RpcManager { + private final Object2LongMap idToMethodMap = new Object2LongOpenHashMap<>(); + private final Long2ObjectMap methodToIdMap = new Long2ObjectOpenHashMap<>(); + + public void handleConfigTask(RpcMethodIdMapPacket pkt) { + var newIdToMethodMap = new Object2LongOpenHashMap(); + var newMethodToIdMap = new Long2ObjectOpenHashMap(); + newIdToMethodMap.putAll(pkt.map()); + + for (var objectEntry : newIdToMethodMap.object2LongEntrySet()) { + if (idToMethodMap.containsKey(objectEntry.getKey())) { + newMethodToIdMap.put(objectEntry.getLongValue(), methodToIdMap.get(idToMethodMap.getLong(objectEntry.getKey()))); + } + } + + idToMethodMap.clear(); + methodToIdMap.clear(); + + idToMethodMap.putAll(newIdToMethodMap); + methodToIdMap.putAll(newMethodToIdMap); + } + + public void acceptConfigTask(Consumer sender) { + sender.accept(new RpcMethodIdMapPacket(idToMethodMap)); + } + + public long getMethodId(String methodName) { + return idToMethodMap.getOrDefault(methodName, -1L); + } + + public void invoke(Object o, long method) { + try { + methodToIdMap.get(method).invoke(o); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + private final Cache, RpcSourceType> cache = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(); + + @Nullable + @SuppressWarnings("unchecked") + public RpcSourceType getType(Object object) { + if (object == null) return null; + + Class oClass = object.getClass(); + RpcSourceType cacheValue = cache.getIfPresent(oClass); + + if (cacheValue != null) { + return (RpcSourceType) cacheValue; + } else { + RpcSourceType newValue = null; + + for (var entry : RpcSourceRegister.REGISTRY.entrySet()) { + if (entry.getValue().clazz().isInstance(object)) { + newValue = entry.getValue(); + } + } + + if (newValue != null) { + cache.put(oClass, newValue); + return (RpcSourceType) newValue; + } + + return null; + } + } + + @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) + public static final class EventListener { + @SubscribeEvent + public static void onEvent(FMLCommonSetupEvent event) { + final long[] id = {0}; + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType voidType = MethodType.methodType(void.class); + + for (ModFileScanData allScanDatum : ModList.get().getAllScanData()) { + allScanDatum.getAnnotatedBy(Rpc.class, ElementType.METHOD).forEach(data -> { + long idIn = id[0]++; + String className = data.clazz().getClassName(); + String methodName = data.memberName().split("\\(\\)")[0]; + idToMethodMap.put(className + "#" + methodName, idIn); + try { + methodToIdMap.put( + idIn, + lookup.findVirtual( + Class.forName(className, false, RpcManager.class.getClassLoader()), + methodName, + voidType + ) + ); + } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }); + } + } + } +} diff --git a/src/main/java/io/github/tt432/chin/rpc/impl/RpcMethodIdMapPacket.java b/src/main/java/io/github/tt432/chin/rpc/impl/RpcMethodIdMapPacket.java new file mode 100644 index 0000000..0121ad6 --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/impl/RpcMethodIdMapPacket.java @@ -0,0 +1,36 @@ +package io.github.tt432.chin.rpc.impl; + +import io.github.tt432.chin.Chin; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author TT432 + */ +public record RpcMethodIdMapPacket( + Map map +) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = + new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Chin.MOD_ID, "rpc_method_id_map")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.map( + HashMap::new, + ByteBufCodecs.STRING_UTF8, + ByteBufCodecs.VAR_LONG + ), + RpcMethodIdMapPacket::map, + RpcMethodIdMapPacket::new + ); + + @Override + public CustomPacketPayload.Type type() { + return TYPE; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkHandler.java b/src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkHandler.java new file mode 100644 index 0000000..b47b4ca --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkHandler.java @@ -0,0 +1,52 @@ +package io.github.tt432.chin.rpc.impl; + +import io.github.tt432.chin.Chin; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.network.ConfigurationTask; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.network.configuration.ICustomConfigurationTask; +import net.neoforged.neoforge.network.event.RegisterConfigurationTasksEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; + +import java.util.function.Consumer; + +/** + * @author TT432 + */ +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) +public class RpcNetworkHandler { + @SubscribeEvent + public static void onEvent(RegisterPayloadHandlersEvent event) { + PayloadRegistrar registrar = event.registrar("1.0").optional(); + + registrar.playBidirectional(RpcNetworkPacket.TYPE, RpcNetworkPacket.STREAM_CODEC, (pkt, ctx) -> { + Object object = pkt.sourceType().objectGetter().getObject(ctx, pkt.sourceId()); + if (object != null) RpcManager.invoke(object, pkt.methodId()); + }); + + registrar.configurationToClient(RpcMethodIdMapPacket.TYPE, RpcMethodIdMapPacket.STREAM_CODEC, + (pkt, ctx) -> RpcManager.handleConfigTask(pkt)); + } + + @SubscribeEvent + public static void onEvent(final RegisterConfigurationTasksEvent event) { + event.register(new ICustomConfigurationTask() { + public static final ConfigurationTask.Type TYPE = + new ConfigurationTask.Type(ResourceLocation.fromNamespaceAndPath(Chin.MOD_ID, "update_rpc_id_map")); + + @Override + public void run(Consumer sender) { + RpcManager.acceptConfigTask(sender); + event.getListener().finishCurrentTask(this.type()); + } + + @Override + public Type type() { + return TYPE; + } + }); + } +} diff --git a/src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkPacket.java b/src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkPacket.java new file mode 100644 index 0000000..0a905a9 --- /dev/null +++ b/src/main/java/io/github/tt432/chin/rpc/impl/RpcNetworkPacket.java @@ -0,0 +1,51 @@ +package io.github.tt432.chin.rpc.impl; + +import io.github.tt432.chin.Chin; +import io.github.tt432.chin.rpc.api.RpcSourceRegister; +import io.github.tt432.chin.rpc.api.RpcSourceType; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +/** + * @author TT432 + */ +public record RpcNetworkPacket( + RpcSourceType sourceType, + long sourceId, + long methodId +) implements CustomPacketPayload { + @SuppressWarnings("unchecked") + public static RpcNetworkPacket of(Object o, String method) { + RpcSourceType type = RpcManager.getType(o); + + if (type == null) return null; + + return new RpcNetworkPacket<>( + type, + type.idGetter().getId((T) o), + RpcManager.getMethodId(method) + ); + } + + public static final Type> TYPE = + new Type<>(ResourceLocation.fromNamespaceAndPath(Chin.MOD_ID, "rpc_network")); + + public static final StreamCodec> STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.registry(RpcSourceRegister.RPC_SOURCE_TYPE_KEY), + RpcNetworkPacket::sourceType, + ByteBufCodecs.VAR_LONG, + RpcNetworkPacket::sourceId, + ByteBufCodecs.VAR_LONG, + RpcNetworkPacket::methodId, + RpcNetworkPacket::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } +} diff --git a/src/main/java/io/github/tt432/chin/util/ChinNetworks.java b/src/main/java/io/github/tt432/chin/util/ChinNetworks.java new file mode 100644 index 0000000..8afb901 --- /dev/null +++ b/src/main/java/io/github/tt432/chin/util/ChinNetworks.java @@ -0,0 +1,31 @@ +package io.github.tt432.chin.util; + +import lombok.experimental.UtilityClass; +import net.minecraft.client.Minecraft; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +import net.neoforged.neoforgespi.Environment; + +/** + * @author TT432 + */ +@UtilityClass +public class ChinNetworks { + public boolean isClientSide() { + if (ServerLifecycleHooks.getCurrentServer() != null + && ServerLifecycleHooks.getCurrentServer().isSingleplayer()) { + return Thread.currentThread().getThreadGroup().getName().equals("main"); + } else { + return Environment.get().getDist().isClient(); + } + } + + public Level getLevel(IPayloadContext context) { + if (isClientSide()) { + return (Level) (Object) Minecraft.getInstance().level; + } else { + return context.player().level(); + } + } +} diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 1b9f6e9..64b908f 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -47,8 +47,8 @@ authors="${mod_authors}" #optional description='''${mod_description}''' # The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. -#[[mixins]] -#config="${mod_id}.mixins.json" +[[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 diff --git a/src/main/resources/chin.mixins.json b/src/main/resources/chin.mixins.json new file mode 100644 index 0000000..0dab062 --- /dev/null +++ b/src/main/resources/chin.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "io.github.tt432.chin.mixin", + "compatibilityLevel": "JAVA_8", + "refmap": "t88.refmap.json", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +}