From f0cd47a9cc67aaf2ff3000d7acbc35133be3dbf0 Mon Sep 17 00:00:00 2001 From: SkyNotTheLimit <159592458+ekulxam@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:22:58 -0700 Subject: [PATCH 1/2] Fix: multipart api improvement Renamed `PartHolder#parts` to `PartHolder#getEntityParts` Renamed `EntityPart.dims` to `EntityPart.dimensions` and made it protected Made `EntityPart.owner` protected Overrode `getDimensions` in `EntityPart` Slight mixin changes `EntityPartWorld` is now an injected interface --- .../ClientWorld$ClientEntityHandlerMixin.java | 22 +++++++++++++------ .../client/entity/EntityRendererMixin.java | 3 ++- .../entity/LivingEntityRendererMixin.java | 3 ++- .../specter/api/entity/EntityPart.java | 19 +++++++++++----- .../specter/api/entity/PartHolder.java | 2 +- .../specter/impl/entity/EntityPartWorld.java | 4 +++- .../mixin/entity/PlayerEntityMixin.java | 4 ++-- .../ServerChunkLoadingManagerMixin.java | 1 + .../ServerWorld$ServerEntityHandlerMixin.java | 17 +++++++++----- .../mixin/entity/ServerWorldMixin.java | 8 +++---- .../specter/mixin/entity/WorldMixin.java | 14 +++++++----- .../src/main/resources/fabric.mod.json | 3 +++ .../entity/mixin/SilverfishEntityMixin.java | 2 +- 13 files changed, 67 insertions(+), 35 deletions(-) diff --git a/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/ClientWorld$ClientEntityHandlerMixin.java b/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/ClientWorld$ClientEntityHandlerMixin.java index ad5efa29..bd9c57d3 100644 --- a/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/ClientWorld$ClientEntityHandlerMixin.java +++ b/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/ClientWorld$ClientEntityHandlerMixin.java @@ -1,32 +1,40 @@ package dev.spiritstudios.specter.mixin.client.entity; + +import org.spongepowered.asm.mixin.Final; 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.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import dev.spiritstudios.specter.api.entity.EntityPart; import dev.spiritstudios.specter.api.entity.PartHolder; -import dev.spiritstudios.specter.impl.entity.EntityPartWorld; @Mixin(targets = "net/minecraft/client/world/ClientWorld$ClientEntityHandler") public abstract class ClientWorld$ClientEntityHandlerMixin { - @Inject(method = "startTracking(Lnet/minecraft/entity/Entity;)V", at = @At("TAIL")) + + @Shadow + @Final + ClientWorld field_27735; + + @Inject(method = "startTracking(Lnet/minecraft/entity/Entity;)V", at = @At("RETURN")) private void startTracking(Entity entity, CallbackInfo ci) { if (entity instanceof PartHolder partHolder) { - for (EntityPart part : partHolder.parts()) { - ((EntityPartWorld) entity.getWorld()).specter$parts().put(part.getId(), part); + for (EntityPart part : partHolder.getEntityParts()) { + this.field_27735.specter$getParts().put(part.getId(), part); } } } - @Inject(method = "stopTracking(Lnet/minecraft/entity/Entity;)V", at = @At("TAIL")) + @Inject(method = "stopTracking(Lnet/minecraft/entity/Entity;)V", at = @At("RETURN")) private void stopTracking(Entity entity, CallbackInfo ci) { if (entity instanceof PartHolder partHolder) { - for (EntityPart part : partHolder.parts()) { - ((EntityPartWorld) entity.getWorld()).specter$parts().remove(part.getId(), part); + for (EntityPart part : partHolder.getEntityParts()) { + this.field_27735.specter$getParts().remove(part.getId(), part); } } } diff --git a/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/EntityRendererMixin.java b/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/EntityRendererMixin.java index ddfb952f..05f2dda4 100644 --- a/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/EntityRendererMixin.java +++ b/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/EntityRendererMixin.java @@ -16,10 +16,11 @@ @Mixin(EntityRenderer.class) public abstract class EntityRendererMixin { + @Inject(method = "appendHitboxes", at = @At("HEAD")) private void appendHitboxes(Entity entity, ImmutableList.Builder builder, float tickProgress, CallbackInfo ci) { if (entity instanceof PartHolder partHolder) { - for (EntityPart part : partHolder.parts()) { + for (EntityPart part : partHolder.getEntityParts()) { Box box = part.getBoundingBox(); builder.add(new EntityHitbox( box.minX - entity.getX(), diff --git a/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/LivingEntityRendererMixin.java b/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/LivingEntityRendererMixin.java index 014b2491..438b93db 100644 --- a/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/LivingEntityRendererMixin.java +++ b/specter-entity/src/client/java/dev/spiritstudios/specter/mixin/client/entity/LivingEntityRendererMixin.java @@ -16,10 +16,11 @@ @Mixin(LivingEntityRenderer.class) public abstract class LivingEntityRendererMixin { + @Inject(method = "appendHitboxes(Lnet/minecraft/entity/LivingEntity;Lcom/google/common/collect/ImmutableList$Builder;F)V", at = @At("HEAD")) private void appendHitboxes(LivingEntity entity, ImmutableList.Builder builder, float tickProgress, CallbackInfo ci) { if (entity instanceof PartHolder partHolder) { - for (EntityPart part : partHolder.parts()) { + for (EntityPart part : partHolder.getEntityParts()) { Box box = part.getBoundingBox(); builder.add(new EntityHitbox( box.minX - entity.getX(), diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/EntityPart.java b/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/EntityPart.java index edd9ed54..e33a0a33 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/EntityPart.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/EntityPart.java @@ -1,5 +1,7 @@ package dev.spiritstudios.specter.api.entity; +import net.minecraft.entity.EntityPose; + import org.jetbrains.annotations.Nullable; import net.minecraft.entity.Entity; @@ -16,14 +18,15 @@ import net.minecraft.util.math.Vec3d; public abstract class EntityPart extends Entity { - private final T owner; - private final EntityDimensions dims; + protected final T owner; + protected final EntityDimensions dimensions; private Vec3d relativePos = new Vec3d(0, 0, 0); public EntityPart(T owner, EntityDimensions dimensions) { super(owner.getType(), owner.getWorld()); this.owner = owner; - this.dims = dimensions; + this.dimensions = dimensions; + this.calculateDimensions(); } @Override @@ -32,18 +35,17 @@ protected void initDataTracker(DataTracker.Builder builder) { @Override protected void readCustomDataFromNbt(NbtCompound nbt) { - } @Override protected void writeCustomDataToNbt(NbtCompound nbt) { - } public Vec3d getRelativePos() { return relativePos; } + @SuppressWarnings("unused") public void setRelativePos(Vec3d relativePos) { this.relativePos = relativePos; } @@ -55,7 +57,7 @@ public boolean canHit() { @Override protected Box calculateDefaultBoundingBox(Vec3d pos) { - return this.dims == null ? super.calculateDefaultBoundingBox(pos) : this.dims.getBoxAt(pos); + return this.dimensions == null ? super.calculateDefaultBoundingBox(pos) : this.dimensions.getBoxAt(pos); } @Override @@ -86,4 +88,9 @@ public Packet createSpawnPacket(EntityTrackerEntry ent public T getOwner() { return owner; } + + @Override + public EntityDimensions getDimensions(EntityPose pose) { + return this.dimensions; + } } diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/PartHolder.java b/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/PartHolder.java index 62c5573a..7747f094 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/PartHolder.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/api/entity/PartHolder.java @@ -5,5 +5,5 @@ import net.minecraft.entity.Entity; public interface PartHolder { - List> parts(); + List> getEntityParts(); } diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/impl/entity/EntityPartWorld.java b/specter-entity/src/main/java/dev/spiritstudios/specter/impl/entity/EntityPartWorld.java index 2c07acb9..005f4f7b 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/impl/entity/EntityPartWorld.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/impl/entity/EntityPartWorld.java @@ -5,5 +5,7 @@ import dev.spiritstudios.specter.api.entity.EntityPart; public interface EntityPartWorld { - Int2ObjectMap> specter$parts(); + default Int2ObjectMap> specter$getParts() { + throw new UnsupportedOperationException("Injected interface should be implemented by mixin!"); + } } diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/PlayerEntityMixin.java b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/PlayerEntityMixin.java index 68db601e..011cbee5 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/PlayerEntityMixin.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/PlayerEntityMixin.java @@ -11,9 +11,9 @@ @Mixin(PlayerEntity.class) public abstract class PlayerEntityMixin { + @ModifyVariable(method = "attack", at = @At("STORE"), ordinal = 1) private Entity attack(Entity value) { - if (value instanceof EntityPart part) return part.getOwner(); - return value; + return value instanceof EntityPart part ? part.getOwner() : value; } } diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerChunkLoadingManagerMixin.java b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerChunkLoadingManagerMixin.java index a07fcb18..5a9228c8 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerChunkLoadingManagerMixin.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerChunkLoadingManagerMixin.java @@ -11,6 +11,7 @@ @Mixin(ServerChunkLoadingManager.class) public abstract class ServerChunkLoadingManagerMixin { + @WrapMethod(method = "loadEntity") private void loadEntity(Entity entity, Operation original) { if (entity instanceof EntityPart) return; diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorld$ServerEntityHandlerMixin.java b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorld$ServerEntityHandlerMixin.java index 9ad823a0..e3f8c564 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorld$ServerEntityHandlerMixin.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorld$ServerEntityHandlerMixin.java @@ -1,23 +1,30 @@ package dev.spiritstudios.specter.mixin.entity; +import org.spongepowered.asm.mixin.Final; 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.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.entity.Entity; +import net.minecraft.server.world.ServerWorld; import dev.spiritstudios.specter.api.entity.EntityPart; import dev.spiritstudios.specter.api.entity.PartHolder; -import dev.spiritstudios.specter.impl.entity.EntityPartWorld; @Mixin(targets = "net/minecraft/server/world/ServerWorld$ServerEntityHandler") public abstract class ServerWorld$ServerEntityHandlerMixin { + + @Shadow + @Final + ServerWorld field_26936; + @Inject(method = "startTracking(Lnet/minecraft/entity/Entity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;updateEventHandler(Ljava/util/function/BiConsumer;)V")) private void startTracking(Entity entity, CallbackInfo ci) { if (entity instanceof PartHolder partHolder) { - for (EntityPart part : partHolder.parts()) { - ((EntityPartWorld) entity.getWorld()).specter$parts().put(part.getId(), part); + for (EntityPart part : partHolder.getEntityParts()) { + this.field_26936.specter$getParts().put(part.getId(), part); } } } @@ -25,8 +32,8 @@ private void startTracking(Entity entity, CallbackInfo ci) { @Inject(method = "stopTracking(Lnet/minecraft/entity/Entity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;updateEventHandler(Ljava/util/function/BiConsumer;)V")) private void stopTracking(Entity entity, CallbackInfo ci) { if (entity instanceof PartHolder partHolder) { - for (EntityPart part : partHolder.parts()) { - ((EntityPartWorld) entity.getWorld()).specter$parts().remove(part.getId(), part); + for (EntityPart part : partHolder.getEntityParts()) { + this.field_26936.specter$getParts().remove(part.getId(), part); } } } diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorldMixin.java b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorldMixin.java index 572e3dff..f329db28 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorldMixin.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/ServerWorldMixin.java @@ -2,18 +2,18 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.sugar.Local; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import net.minecraft.entity.Entity; import net.minecraft.server.world.ServerWorld; -import dev.spiritstudios.specter.impl.entity.EntityPartWorld; - @Mixin(ServerWorld.class) -public abstract class ServerWorldMixin implements EntityPartWorld { +public abstract class ServerWorldMixin extends WorldMixin { + @ModifyReturnValue(method = "getEntityOrDragonPart", at = @At("RETURN")) private Entity getEntityOrDragonPart(Entity original, @Local(argsOnly = true) int id) { - return original != null ? original : this.specter$parts().get(id); + return original != null ? original : this.specter$parts.get(id); } } diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java index 2f32a094..262cdc10 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java @@ -7,6 +7,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -25,10 +26,11 @@ @Mixin(World.class) public abstract class WorldMixin implements EntityPartWorld { + @Unique - private final Int2ObjectMap> parts = new Int2ObjectOpenHashMap<>(); + protected final Int2ObjectMap> specter$parts = new Int2ObjectOpenHashMap<>(); - @Inject(method = "method_47576", at = @At("TAIL"), cancellable = true) + @Inject(method = "method_47576", at = @At("RETURN"), cancellable = true) private static void collectEntitiesByTypeLambda( Predicate predicate, List result, @@ -38,7 +40,7 @@ private static void collectEntitiesByTypeLambda( CallbackInfoReturnable cir ) { if (entity instanceof PartHolder partHolder) { - for (EntityPart part : partHolder.parts()) { + for (EntityPart part : partHolder.getEntityParts()) { T partCasted = filter.downcast(part); if (partCasted != null && predicate.test(partCasted)) { @@ -55,7 +57,7 @@ private static void collectEntitiesByTypeLambda( private List getOtherEntities(Entity except, Box box, Predicate predicate, Operation> original) { List list = original.call(except, box, predicate); - for (EntityPart part : this.parts.values()) { + for (EntityPart part : this.specter$parts.values()) { if (part != except && part.getOwner() != except && predicate.test(part) && box.intersects(part.getBoundingBox())) { list.add(part); } @@ -65,7 +67,7 @@ private List getOtherEntities(Entity except, Box box, Predicate> specter$parts() { - return parts; + public Int2ObjectMap> specter$getParts() { + return specter$parts; } } diff --git a/specter-entity/src/main/resources/fabric.mod.json b/specter-entity/src/main/resources/fabric.mod.json index 0c6df94e..39634c21 100644 --- a/specter-entity/src/main/resources/fabric.mod.json +++ b/specter-entity/src/main/resources/fabric.mod.json @@ -35,6 +35,9 @@ "badges": [ "library" ] + }, + "loom:injected_interfaces": { + "net/minecraft/class_1937": ["dev/spiritstudios/specter/impl/entity/EntityPartWorld"] } } } diff --git a/specter-entity/src/testmod/java/dev/spiritstudios/testmod/entity/mixin/SilverfishEntityMixin.java b/specter-entity/src/testmod/java/dev/spiritstudios/testmod/entity/mixin/SilverfishEntityMixin.java index be151046..01a81ddf 100644 --- a/specter-entity/src/testmod/java/dev/spiritstudios/testmod/entity/mixin/SilverfishEntityMixin.java +++ b/specter-entity/src/testmod/java/dev/spiritstudios/testmod/entity/mixin/SilverfishEntityMixin.java @@ -30,7 +30,7 @@ protected SilverfishEntityMixin(EntityType entityType, } @Override - public List> parts() { + public List> getEntityParts() { return parts; } From 33e88a34d75903b784657f42cbf9c486888fc527 Mon Sep 17 00:00:00 2001 From: SkyNotTheLimit <159592458+ekulxam@users.noreply.github.com> Date: Sat, 14 Jun 2025 12:06:21 -0700 Subject: [PATCH 2/2] Fix: Inject at tail and not return --- .../java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java index 262cdc10..13555f5b 100644 --- a/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java +++ b/specter-entity/src/main/java/dev/spiritstudios/specter/mixin/entity/WorldMixin.java @@ -30,7 +30,7 @@ public abstract class WorldMixin implements EntityPartWorld { @Unique protected final Int2ObjectMap> specter$parts = new Int2ObjectOpenHashMap<>(); - @Inject(method = "method_47576", at = @At("RETURN"), cancellable = true) + @Inject(method = "method_47576", at = @At("TAIL"), cancellable = true) private static void collectEntitiesByTypeLambda( Predicate predicate, List result,