SpongePowered / Mixin

Mixin is a trait/mixin and bytecode weaving framework for Java using ASM
MIT License
1.37k stars 185 forks source link

Call mixed-in method or get mixed-in field #639

Closed xc0ff33 closed 11 months ago

xc0ff33 commented 11 months ago

This is probably really obvious (I'm very new to mixins and modding with fabric in general) but I need to call a method that a mixin adds to a class from another class that isn't a mixin.

In more depth: I need to call a method called setFX which is added to Entity by a mixin EntitySetFXMixin from a class LightningFX that isn't a mixin.

This isn't it though. I have to do the same thing, but instead with a boolean field. The field is called isFX and is also added to Entity by EntitySetFXMixin. The setFX method sets isFX (both are in Entity) to true. I need to get this field from a mixin LightningFXMixin that targets LightningEntity. (LightningFXMixin redirects the spawnFire() method in LightningEntity to only spawn fire if isFX is false of the lightning object)

Entity is the superclass of LightningEntity, so I should be able to call LightningEntity.setFX(). Obviously, IntelliJ won't compile this, as no method exists. I tried using a hacky solution using getMethod() and getBoolean(), but the game crashed when I tried to call setFX even if it existed at the time (.mixin.out showed me so). Is there something I'm doing wrong?

All mixins are loaded on both the client and the server.


Here is my code: **LightningFXMixin.java** - This is what's getting `isFX` from `Entity`! ``` package modid.mixin.fx.lightning; import net.minecraft.entity.LightningEntity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(LightningEntity.class) public abstract class LightningFXMixin { @Shadow protected abstract void spawnFire(int spreadAttempts); @Redirect(method = "tick()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LightningEntity;spawnFire(I)V")) private void injected(LightningEntity instance, int spreadAttempts) { if (!this.isFX) { this.spawnFire(spreadAttempts); } } } ``` **EntitySetFXMixin** - This is what's mixing in `isFX` and `setFX()` into `Entity`! ``` // all this compiles and gets mixed in package modid.mixin.fx; import net.minecraft.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @Mixin(Entity.class) public class EntitySetFXMixin { @Unique public boolean isFX = false; @Unique public void setFX() { isFX = true; } } ``` **LightningFX** - This is what creates the `LightningEntity` object and spawns it in, calling `setFX` on it! ``` package modid.fx; import net.minecraft.entity.EntityType; import net.minecraft.entity.SpawnReason; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.entity.LightningEntity; public class LightningFX { public void create(ServerWorld world, BlockPos pos) { EntityType lightning = EntityType.LIGHTNING_BOLT; lightning.spawn(world, null, null, pos.up(2), SpawnReason.TRIGGERED, false, false); this.setFX(); } } ```
Here was my hacky solution: (this 90% wasn't the correct way to do it) **Calling `setFX` in `LightningFX`** ``` package modid.fx; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.SpawnReason; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.entity.LightningEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.*; public class LightningKillFX { public void create(ServerWorld world, BlockPos pos) { EntityType lightning = EntityType.LIGHTNING_BOLT; Entity entity = lightning.spawn(world, null, null, pos.up(2), SpawnReason.TRIGGERED, false, false); Class entityClass; if (entity != null) { entityClass = entity.getClass(); try { Method method = entityClass.getMethod("setFX", Entity.class); method.invoke(entity, entity); } catch (Exception e) { Logger LOGGER = LoggerFactory.getLogger("modid"); LOGGER.info("Couldn't find method setFX() (assuming EntitySetFXMixin didn't load), skipping Lightning FX"); } } } } ``` **Getting `isFX` from `LightningFXMixin`** ``` package modid.mixin.fx.lightning; import net.minecraft.entity.LightningEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; import java.lang.reflect.Field; @Mixin(LightningEntity.class) public abstract class LightningKillFXMixin { @Shadow protected abstract void spawnFire(int spreadAttempts); @Redirect(method = "tick()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LightningEntity;spawnFire(I)V")) private void injected(LightningEntity instance, int spreadAttempts) { LightningEntity lightning = ((LightningEntity) (Object) this); boolean fxBool = false; try { Field field = LightningEntity.class.getField("isFX"); fxBool = field.getBoolean(lightning); } catch (Exception e) { Logger LOGGER = LoggerFactory.getLogger("modid"); LOGGER.info("Couldn't find field isFX (assuming EntitySetFXMixin didn't load), spawning lightning fire"); } if (!fxBool) { this.spawnFire(spreadAttempts); } } } ```
Mumfrey commented 11 months ago

This is covered in the very first documentation chapter, please read it.

This is an issue tracker not a support forum so please keep development questions in discord.

LlamaLad7 commented 11 months ago

This is an issue tracker, use the discord for support questions. Though for a quick answer your LightningEntity mixin can extend your Entity mixin, and you need to use an interface to expose the setter to your utility class. That's explained in page 1 of the GitHub wiki, which you should read if you haven't already. Also, @Unique is not helpful on public members, instead prefix them with your modid to avoid conflicts.

LlamaLad7 commented 11 months ago

Oops, bad timing :P