diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java new file mode 100644 index 00000000..923ca57c --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java @@ -0,0 +1,97 @@ +package org.embeddedt.modernfix.common.mixin.bugfix.missing_block_entities; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Hypixel (and possibly other buggy servers) send chunks to the client that are missing some block entity data, which + * causes these entities to be invisible. We "fix" this by recreating the block entity on the client with default data, + * which is hopefully what the legacy server also expects. + */ +@Mixin(LevelChunk.class) +@ClientOnlyMixin +public abstract class LevelChunkMixin extends ChunkAccess { + @Shadow @Final private Level level; + + @Shadow @Nullable public abstract BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType); + + public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable BlendingData blendingData) { + super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData); + } + + @Inject(method = "replaceWithPacketData", at = @At("RETURN")) + private void validateBlockEntitiesInChunk(CallbackInfo ci) { + // No reason to check in singleplayer or on the integrated server + if (this.level.isClientSide && !Minecraft.getInstance().isLocalServer()) { + for (int i = 0; i < this.sections.length; i++) { + var section = this.sections[i]; + try { + if (!section.hasOnlyAir() && section.maybeHas(BlockBehaviour.BlockStateBase::hasBlockEntity)) { + scanSectionForBlockEntities(section, i); + } + } catch(Exception e) { + ModernFix.LOGGER.error("Exception validating data in chunk", e); + return; + } + } + } + } + + @Unique + private void scanSectionForBlockEntities(LevelChunkSection section, int i) { + int chunkXOff = this.chunkPos.x * 16; + int chunkZOff = this.chunkPos.z * 16; + BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + int sectionYOff = this.getSectionYFromSectionIndex(i) * 16; + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + var state = section.getBlockState(x, y, z); + if (state.hasBlockEntity()) { + cursor.set(chunkXOff + x, sectionYOff + y, chunkZOff + z); + makeBlockEntityIfNotExists(state, cursor); + } + } + } + } + } + + @Unique + private void makeBlockEntityIfNotExists(BlockState state, BlockPos.MutableBlockPos pos) { + if (this.blockEntities.containsKey(pos) || this.pendingBlockEntities.containsKey(pos)) { + return; + } + + BlockEntity blockEntity = this.getBlockEntity(pos.immutable(), LevelChunk.EntityCreationType.IMMEDIATE); + String blockName = state.getBlock().toString(); + if (blockEntity != null) { + ModernFix.LOGGER.warn("Created missing block entity for {} at {}", blockName, pos.toShortString()); + } else { + ModernFix.LOGGER.error("Block entity is missing for {} at {}, but could not be created", blockName, pos.toShortString()); + } + } +} + diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/ClientTelemetryManagerMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/ClientTelemetryManagerMixin.java new file mode 100644 index 00000000..a890c749 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/ClientTelemetryManagerMixin.java @@ -0,0 +1,22 @@ +package org.embeddedt.modernfix.common.mixin.feature.remove_telemetry; + +import net.minecraft.client.telemetry.ClientTelemetryManager; +import net.minecraft.client.telemetry.TelemetryEventSender; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ClientTelemetryManager.class, priority = 1500) +@ClientOnlyMixin +public class ClientTelemetryManagerMixin { + /** + * @author embeddedt + * @reason telemetry is useless noise for modded instances anyway, and introduces privacy concerns + */ + @Inject(method = "createEventSender", at = @At("HEAD"), cancellable = true) + private void disableTelemetrySender(CallbackInfoReturnable cir) { + cir.setReturnValue(TelemetryEventSender.DISABLED); + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/MinecraftMixin_Telemetry.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/MinecraftMixin_Telemetry.java new file mode 100644 index 00000000..e3fa63b8 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/remove_telemetry/MinecraftMixin_Telemetry.java @@ -0,0 +1,17 @@ +package org.embeddedt.modernfix.common.mixin.feature.remove_telemetry; + +import net.minecraft.client.Minecraft; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = Minecraft.class, priority = 1100) +@ClientOnlyMixin +public class MinecraftMixin_Telemetry { + @Inject(method = "allowsTelemetry", at = @At("HEAD"), cancellable = true) + private void markTelemetryNotAllowed(CallbackInfoReturnable cir) { + cir.setReturnValue(false); + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 8d7b00ae..f113e866 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -166,6 +166,7 @@ public class ModernFixEarlyConfig { .put("mixin.bugfix.restore_old_dragon_movement", false) .put("mixin.perf.worldgen_allocation", false) // experimental .put("mixin.feature.cause_lag_by_disabling_threads", false) + .put("mixin.bugfix.missing_block_entities", false) .put("mixin.perf.clear_mixin_classinfo", false) .put("mixin.perf.deduplicate_climate_parameters", false) .put("mixin.bugfix.packet_leak", false) diff --git a/common/src/main/resources/assets/modernfix/lang/en_us.json b/common/src/main/resources/assets/modernfix/lang/en_us.json index 7ab5e016..e18097bc 100644 --- a/common/src/main/resources/assets/modernfix/lang/en_us.json +++ b/common/src/main/resources/assets/modernfix/lang/en_us.json @@ -134,5 +134,6 @@ "modernfix.option.mixin.perf.fix_loop_spin_waiting": "Fixes Minecraft's built-in wait function consuming excessive amounts of CPU resources.", "modernfix.option.mixin.perf.forge_cap_retrieval": "Small micro-optimization that makes retrieving custom entity data slightly more efficient on Forge.", "modernfix.option.mixin.perf.forge_registry_lambda": "Fixes oversights in Forge that lead to excessive allocation in hot registry methods.", - "modernfix.option.mixin.bugfix.restore_old_dragon_movement": "Fixes MC-272431, which tracks the ender dragon being unable to dive to the portal as it did in 1.13 and older. This causes the dragon to fly quite a bit differently from what modern players are used to and also patches out one-cycling, so it's not enabled by default. Thanks to Jukitsu for identifying the regression in the vanilla code." + "modernfix.option.mixin.bugfix.restore_old_dragon_movement": "Fixes MC-272431, which tracks the ender dragon being unable to dive to the portal as it did in 1.13 and older. This causes the dragon to fly quite a bit differently from what modern players are used to and also patches out one-cycling, so it's not enabled by default. Thanks to Jukitsu for identifying the regression in the vanilla code.", + "modernfix.option.mixin.bugfix.missing_block_entities": "Hypixel sends chunks to the client that are missing some block entity data, which makes chests etc. appear invisible. This 'fixes' the problem by creating the needed data on the client. Has no effect for properly behaved servers or in singleplayer." } diff --git a/gradle.properties b/gradle.properties index 44a5a73e..ef590210 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,7 @@ refined_storage_version=4392788 jei_version=19.0.0.9 rei_version=13.0.678 ctm_version=1.21-1.2.0+2 +ldlib_version=5782845 kubejs_version=1902.6.0-build.142 rhino_version=1902.2.2-build.268 supported_minecraft_versions=1.21.4 diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 85aaf99e..6fa89b60 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -55,6 +55,7 @@ dependencies { modCompileOnly("dev.latvian.mods:kubejs-forge:${kubejs_version}") //modRuntimeOnly("curse.maven:ferritecore-429235:4441949") modCompileOnly("team.chisel.ctm:CTM:${ctm_version}") + modCompileOnly("curse.maven:ldlib-626676:${ldlib_version}") modCompileOnly("curse.maven:supermartijncore-454372:4455391") modCompileOnly("vazkii.patchouli:Patchouli:1.19.2-77") diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ldlib/ClientProxyImplMixin.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ldlib/ClientProxyImplMixin.java new file mode 100644 index 00000000..44dce277 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ldlib/ClientProxyImplMixin.java @@ -0,0 +1,100 @@ +package org.embeddedt.modernfix.neoforge.mixin.perf.dynamic_resources.ldlib; + +import com.lowdragmc.lowdraglib.LDLib; +import com.lowdragmc.lowdraglib.client.ClientProxy; +import com.lowdragmc.lowdraglib.client.model.custommodel.CustomBakedModel; +import com.lowdragmc.lowdraglib.client.model.custommodel.LDLMetadataSection; +import com.lowdragmc.lowdraglib.client.model.forge.LDLRendererModel; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import org.embeddedt.modernfix.ModernFixClient; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.annotation.RequiresMod; +import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Mixin(ClientProxy.class) +@ClientOnlyMixin +@RequiresMod("ldlib") +public abstract class ClientProxyImplMixin implements ModernFixClientIntegration { + @Inject(method = "", at = @At("RETURN")) + private void registerIntegration(CallbackInfo ci) { + ModernFixClient.CLIENT_INTEGRATIONS.add(this); + } + + @Redirect(method = "modelBake", at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;", ordinal = 0), remap = false) + private Set disableLoop(Map map) { + return Set.of(); + } + + @Override + public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) { + if (baked == null) { + return null; + } + if (rootModel != null) { + if (baked instanceof LDLRendererModel) { + return baked; + } + if (baked.isCustomRenderer()) { // Nothing we can add to builtin models + return baked; + } + Deque dependencies = new ArrayDeque<>(); + Set seenModels = new HashSet<>(); + ResourceLocation rl = mrl.id(); + dependencies.push(rl); + seenModels.add(rl); + boolean shouldWrap = ClientProxy.WRAPPED_MODELS.getOrDefault(mrl, false); + // Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles + while (!shouldWrap && !dependencies.isEmpty()) { + ResourceLocation dep = dependencies.pop(); + UnbakedModel model; + try { + model = dep == rl ? rootModel : bakery.getModel(dep); + } catch (Exception e) { + continue; + } + try { + Set textures = new HashSet<>(ClientProxy.SCRAPED_TEXTURES.get(dep)); + for (Material tex : textures) { + // Cache all dependent texture metadata + // At least one texture has CTM metadata, so we should wrap this baked + if (!LDLMetadataSection.getMetadata(LDLMetadataSection.spriteToAbsolute(tex.texture())).isMissing()) { // TODO lazy + shouldWrap = true; + break; + } + } + if (!shouldWrap) { + for (ResourceLocation newDep : model.getDependencies()) { + if (seenModels.add(newDep)) { + dependencies.push(newDep); + } + } + } + } catch (Exception e) { + LDLib.LOGGER.error("Error loading baked dependency {} for baked {}. Skipping...", dep, rl, e); + } + } + ClientProxy.WRAPPED_MODELS.put(mrl, shouldWrap); + if (shouldWrap) { + return new CustomBakedModel<>(baked); + } + } + return baked; + } +}