Skip to content

Commit

Permalink
Improve multipart entity support
Browse files Browse the repository at this point in the history
  • Loading branch information
BluSpring committed Jan 2, 2025
1 parent 4e42644 commit b17c319
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 5 deletions.
2 changes: 1 addition & 1 deletion forge
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
// TRACKED HASH: 7806cbd7ecf0842aa5db2c08ecd295f2b0b0f3ed
package xyz.bluspring.kilt.forgeinjects.client.renderer.entity;

import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import io.github.fabricators_of_create.porting_lib.entity.MultiPartEntity;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.client.event.EntityRenderersEvent;
import net.minecraftforge.entity.PartEntity;
import net.minecraftforge.fml.ModLoader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
Expand Down Expand Up @@ -36,4 +48,31 @@ public Map<String, EntityRenderer<? extends Player>> getSkinMap() {
private void kilt$addEntityRenderLayers(ResourceManager resourceManager, CallbackInfo ci, @Local EntityRendererProvider.Context context) {
ModLoader.get().postEvent(new EntityRenderersEvent.AddLayers(this.renderers, this.playerRenderers, context));
}

@Definition(id = "entity", local = @Local(type = Entity.class, argsOnly = true))
@Definition(id = "EnderDragon", type = EnderDragon.class)
@Expression("entity instanceof EnderDragon")
@WrapOperation(method = "renderHitbox", at = @At("MIXINEXTRAS:EXPRESSION"))
private static boolean kilt$renderHitboxIfMultipart(Object object, Operation<Boolean> original, @Local(argsOnly = true) PoseStack poseStack, @Local(argsOnly = true) VertexConsumer buffer, @Local(argsOnly = true) float partialTicks, @Local(argsOnly = true) Entity entity) {
if (original.call(object))
return true;

if (((Entity) object).isMultipartEntity() && !(object instanceof MultiPartEntity)) {
double currentX = -Mth.lerp(partialTicks, entity.xOld, entity.getX());
double currentY = -Mth.lerp(partialTicks, entity.yOld, entity.getY());
double currentZ = -Mth.lerp(partialTicks, entity.zOld, entity.getZ());

for (PartEntity<?> partEntity : entity.getParts()) {
poseStack.pushPose();
double partX = currentX + Mth.lerp(partialTicks, partEntity.xOld, partEntity.getX());
double partY = currentY + Mth.lerp(partialTicks, partEntity.yOld, partEntity.getY());
double partZ = currentZ + Mth.lerp(partialTicks, partEntity.zOld, partEntity.getZ());
poseStack.translate(partX, partY, partZ);
LevelRenderer.renderLineBox(poseStack, buffer, partEntity.getBoundingBox().move(-partEntity.getX(), -partEntity.getY(), -partEntity.getZ()), 0.25F, 1.0F, 0.0F, 1.0F);
poseStack.popPose();
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
// TRACKED HASH: cebcc0747792b8bfe53d24573c9609f5c22b61d1
package xyz.bluspring.kilt.forgeinjects.server.level;

import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.entity.PartEntity;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.level.ChunkDataEvent;
import net.minecraftforge.event.level.ChunkEvent;
Expand All @@ -30,6 +37,14 @@
public abstract class ChunkMapInject {
@Shadow @Final private ServerLevel level;

@Definition(id = "entity", local = @Local(type = Entity.class, argsOnly = true))
@Definition(id = "EnderDragonPart", type = EnderDragonPart.class)
@Expression("entity instanceof EnderDragonPart")
@WrapOperation(method = "addEntity", at = @At("MIXINEXTRAS:EXPRESSION"))
private boolean kilt$checkIfEntityMultipart(Object object, Operation<Boolean> original) {
return original.call(object) || object instanceof PartEntity<?>;
}

@Inject(method = "updateChunkScheduling", at = @At(value = "RETURN", ordinal = 1))
private void kilt$fireTicketUpdatedEvent(long chunkPos, int newLevel, ChunkHolder holder, int oldLevel, CallbackInfoReturnable<ChunkHolder> cir) {
ForgeEventFactory.fireChunkTicketLevelUpdated(this.level, chunkPos, oldLevel, newLevel, holder);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,54 @@
// TRACKED HASH: 853e9e4639ba453c25a048d0905ec758c41a51dd
package xyz.bluspring.kilt.forgeinjects.server.level;

import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.entity.EntityAccess;
import net.minecraft.world.level.entity.LevelCallback;
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.extensions.IForgeLevel;
import net.minecraftforge.common.util.LevelCapabilityData;
import net.minecraftforge.entity.PartEntity;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.entity.EntityLeaveLevelEvent;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
Expand All @@ -26,21 +57,26 @@
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import xyz.bluspring.kilt.injections.server.level.ServerLevelInjection;

import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;

@Mixin(ServerLevel.class)
public abstract class ServerLevelInject extends Level {
public abstract class ServerLevelInject extends Level implements ServerLevelInjection, IForgeLevel {
protected ServerLevelInject(WritableLevelData levelData, ResourceKey<Level> dimension, RegistryAccess registryAccess, Holder<DimensionType> dimensionTypeRegistration, Supplier<ProfilerFiller> profiler, boolean isClientSide, boolean isDebug, long biomeZoomSeed, int maxChainedNeighborUpdates) {
super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates);
}

@Shadow public abstract DimensionDataStorage getDataStorage();

@Unique
private LevelCapabilityData capabilityData;
@Shadow @Final private List<ServerPlayer> players;
@Unique private LevelCapabilityData capabilityData;
@Unique final Int2ObjectMap<PartEntity<?>> kilt$entityParts = new Int2ObjectOpenHashMap<>();

@Inject(method = "<init>", at = @At("TAIL"))
private void kilt$addInitCapabilities(MinecraftServer server, Executor dispatcher, LevelStorageSource.LevelStorageAccess levelStorageAccess, ServerLevelData serverLevelData, ResourceKey dimension, LevelStem levelStem, ChunkProgressListener progressListener, boolean isDebug, long biomeZoomSeed, List customSpawners, boolean tickTime, RandomSequences randomSequences, CallbackInfo ci) {
Expand All @@ -57,9 +93,142 @@ protected ServerLevelInject(WritableLevelData levelData, ResourceKey<Level> dime
return ForgeEventFactory.onSleepFinished((ServerLevel) (Object) this, l, ((ServerLevel) (Object) this).getDayTime());
}

@WrapWithCondition(method = "method_31420", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;guardEntityTick(Ljava/util/function/Consumer;Lnet/minecraft/world/entity/Entity;)V"))
private boolean kilt$checkValidTickingEntity(ServerLevel instance, Consumer consumer, Entity entity) {
return !entity.isRemoved() && !(entity instanceof PartEntity<?>);
}

@WrapOperation(method = "tickChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/biome/Biome;shouldFreeze(Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z"))
private boolean kilt$checkAreaLoaded(Biome instance, LevelReader level, BlockPos pos, Operation<Boolean> original) {
return this.isAreaLoaded(pos, 1) && original.call(instance, level, pos);
}

@WrapWithCondition(method = "tickPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;rideTick()V"))
private boolean kilt$checkIfEntityCanUpdate(Entity instance) {
return instance.canUpdate();
}

@WrapOperation(method = "addEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;addNewEntity(Lnet/minecraft/world/level/entity/EntityAccess;)Z"))
private <T extends EntityAccess> boolean kilt$checkEntityAddToWorld(PersistentEntitySectionManager<T> instance, T entity, Operation<Boolean> original) {
//noinspection MixinExtrasOperationParameters
if (original.call(instance, entity)) {
((Entity) entity).onAddedToWorld();
return true;
} else {
return false;
}
}

@Inject(method = "playSeededSound(Lnet/minecraft/world/entity/player/Player;DDDLnet/minecraft/core/Holder;Lnet/minecraft/sounds/SoundSource;FFJ)V", at = @At("HEAD"), cancellable = true)
private void kilt$modifyPositionedSound(CallbackInfo ci, @Local(argsOnly = true, ordinal = 0) double x, @Local(argsOnly = true, ordinal = 1) double y, @Local(argsOnly = true, ordinal = 2) double z, @Local(argsOnly = true) LocalRef<Holder<SoundEvent>> sound, @Local(argsOnly = true) LocalRef<SoundSource> source, @Local(argsOnly = true, ordinal = 0) LocalFloatRef volume, @Local(argsOnly = true, ordinal = 1) LocalFloatRef pitch, @Local(argsOnly = true) long seed) {
var event = ForgeEventFactory.onPlaySoundAtPosition(this, x, y, z, sound.get(), source.get(), volume.get(), pitch.get());

if (event.isCanceled() || event.getSound() == null) {
ci.cancel();
return;
}

sound.set(event.getSound());
source.set(event.getSource());
volume.set(event.getNewVolume());
pitch.set(event.getNewPitch());
}

@Inject(method = "playSeededSound(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/core/Holder;Lnet/minecraft/sounds/SoundSource;FFJ)V", at = @At("HEAD"), cancellable = true)
private void kilt$modifyEntitySound(CallbackInfo ci, @Local(argsOnly = true) Entity entity, @Local(argsOnly = true) LocalRef<Holder<SoundEvent>> sound, @Local(argsOnly = true) LocalRef<SoundSource> source, @Local(argsOnly = true, ordinal = 0) LocalFloatRef volume, @Local(argsOnly = true, ordinal = 1) LocalFloatRef pitch, @Local(argsOnly = true) long seed) {
var event = ForgeEventFactory.onPlaySoundAtEntity(entity, sound.get(), source.get(), volume.get(), pitch.get());

if (event.isCanceled() || event.getSound() == null) {
ci.cancel();
return;
}

sound.set(event.getSound());
source.set(event.getSource());
volume.set(event.getNewVolume());
pitch.set(event.getNewPitch());
}

@Inject(method = "gameEvent", at = @At("HEAD"), cancellable = true)
private void kilt$callVanillaGameEvent(GameEvent event, Vec3 position, GameEvent.Context context, CallbackInfo ci) {
if (!ForgeHooks.onVanillaGameEvent(this, event, position, context))
ci.cancel();
}

@Inject(method = "updateNeighborsAt", at = @At("HEAD"))
private void kilt$notifyNeighborsEvent(BlockPos pos, Block block, CallbackInfo ci) {
ForgeEventFactory.onNeighborNotify(this, pos, this.getBlockState(pos), EnumSet.allOf(Direction.class), false)
.isCanceled(); // TODO: what's this for? why is this in Forge's patch?
}

@Inject(method = "updateNeighborsAtExceptFromFacing", at = @At("HEAD"), cancellable = true)
private void kilt$notifyNeighborsEventWithoutFacing(BlockPos pos, Block blockType, Direction skipSide, CallbackInfo ci) {
var directions = EnumSet.allOf(Direction.class);
directions.remove(skipSide);

if (ForgeEventFactory.onNeighborNotify(this, pos, this.getBlockState(pos), directions, false).isCanceled())
ci.cancel();
}

protected void initCapabilities() {
this.gatherCapabilities();
capabilityData = this.getDataStorage().computeIfAbsent(e -> LevelCapabilityData.load(e, this.getCapabilities()), () -> new LevelCapabilityData(getCapabilities()), LevelCapabilityData.ID);
capabilityData.setCapabilities(getCapabilities());
}

@Override
public Int2ObjectMap<PartEntity<?>> kilt$getEntityParts() {
return this.kilt$entityParts;
}

@Override
public Collection<PartEntity<?>> kilt$getPartEntities() {
return this.kilt$entityParts.values();
}

@Mixin(targets = "net.minecraft.server.level.ServerLevel.EntityCallbacks")
public static abstract class EntityCallbacksInject implements LevelCallback<Entity> {
@Shadow @Final
ServerLevel field_26936;

@Definition(id = "entity", local = @Local(type = Entity.class, argsOnly = true))
@Definition(id = "EnderDragon", type = EnderDragon.class)
@Expression("entity instanceof EnderDragon")
@WrapOperation(method = "onTrackingStart(Lnet/minecraft/world/entity/Entity;)V", at = @At("MIXINEXTRAS:EXPRESSION"))
private boolean kilt$startTrackingMultipart(Object object, Operation<Boolean> original) {
if (original.call(object))
return true;

if (((Entity) object).isMultipartEntity()) {
for (PartEntity<?> part : ((Entity) object).getParts()) {
((ServerLevelInjection) field_26936).kilt$getEntityParts().put(part.getId(), part);
}
}

return false;
}

@Definition(id = "entity", local = @Local(type = Entity.class, argsOnly = true))
@Definition(id = "EnderDragon", type = EnderDragon.class)
@Expression("entity instanceof EnderDragon")
@WrapOperation(method = "onTrackingEnd(Lnet/minecraft/world/entity/Entity;)V", at = @At("MIXINEXTRAS:EXPRESSION"))
private boolean kilt$stopTrackingMultipart(Object object, Operation<Boolean> original) {
if (original.call(object))
return true;

if (((Entity) object).isMultipartEntity()) {
for (PartEntity<?> part : ((Entity) object).getParts()) {
((ServerLevelInjection) field_26936).kilt$getEntityParts().remove(part.getId());
}
}

return false;
}

@Inject(method = "onTrackingEnd(Lnet/minecraft/world/entity/Entity;)V", at = @At("TAIL"))
private void kilt$callEntityLevelRemoveEvent(Entity entity, CallbackInfo ci) {
entity.onRemovedFromWorld();
MinecraftForge.EVENT_BUS.post(new EntityLeaveLevelEvent(entity, field_26936));
}
}
}
Loading

0 comments on commit b17c319

Please sign in to comment.