diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index 01ee30c7..7e5a53fd 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -9,12 +9,14 @@ import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.server.FMLServerStartedEvent; +import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.network.FMLNetworkConstants; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.core.config.ModernFixConfig; +import org.embeddedt.modernfix.structure.AsyncLocator; import java.lang.management.ManagementFactory; diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index f1b5a714..35a1b441 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -42,6 +42,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("perf.faster_baking", true); this.addMixinRule("perf.cache_model_materials", true); this.addMixinRule("perf.datapack_reload_exceptions", true); + this.addMixinRule("perf.async_locator", true); /* Keep this off if JEI isn't installed to prevent breaking vanilla gameplay */ this.addMixinRule("perf.blast_search_trees", FMLLoader.getLoadingModList().getModFileById("jei") != null); this.addMixinRule("safety", true); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/CommandSourceStackAccess.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/CommandSourceStackAccess.java new file mode 100644 index 00000000..e8073943 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/CommandSourceStackAccess.java @@ -0,0 +1,12 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CommandSourceStack.class) +public interface CommandSourceStackAccess { + @Accessor + CommandSource getSource(); +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/DolphinSwimToTreasureGoalMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/DolphinSwimToTreasureGoalMixin.java new file mode 100644 index 00000000..bd79c9a0 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/DolphinSwimToTreasureGoalMixin.java @@ -0,0 +1,109 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.animal.Dolphin; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.structure.AsyncLocator; +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(targets = "net.minecraft.world.entity.animal.Dolphin$DolphinSwimToTreasureGoal") +public class DolphinSwimToTreasureGoalMixin { + @Final + @Shadow + private Dolphin dolphin; + + @Shadow + private boolean stuck; + + private AsyncLocator.LocateTask locateTask = null; + + /* + Intercept DolphinSwimToTreasureGoal#start call right before it calls ServerLevel#findNearestMapFeature to pass + the logic over to an async task. + */ + @Inject( + method = "start", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;" + ), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILSOFT + ) + public void findTreasureAsync(CallbackInfo ci, ServerLevel level, BlockPos blockpos) { + ModernFix.LOGGER.debug("Intercepted DolphinSwimToTreasureGoal#start call"); + handleFindTreasureAsync(level, blockpos); + ci.cancel(); + } + + /* + Intercept DolphinSwimToTreasureGoal#canContinueToUse to return true if an async locating task is ongoing so that + the goal isn't ended early due to no treasure pos being set yet. + */ + @Inject( + method = "canContinueToUse", + at = @At(value = "HEAD"), + cancellable = true + ) + public void continueToUseIfLocatingTreasure(CallbackInfoReturnable cir) { + if (locateTask != null) { + ModernFix.LOGGER.debug("Locating task ongoing - returning true for continueToUse()"); + cir.setReturnValue(true); + } + } + + @Inject( + method = "stop", + at = @At(value = "HEAD") + ) + public void stopLocatingTreasure(CallbackInfo ci) { + if (locateTask != null) { + ModernFix.LOGGER.debug("Locating task ongoing - cancelling during stop()"); + locateTask.cancel(); + locateTask = null; + } + } + + /* + Intercept DolphinSwimToTreasureGoal#tick to return early if an async locating task is ongoing so that the + dolphin doesn't try to go towards an old treasure position. + */ + @Inject( + method = "tick", + at = @At(value = "HEAD"), + cancellable = true + ) + public void skipTickingIfLocatingTreasure(CallbackInfo ci) { + if (locateTask != null) { + ModernFix.LOGGER.debug("Locating task ongoing - skipping tick()"); + ci.cancel(); + } + } + + private void handleFindTreasureAsync(ServerLevel level, BlockPos blockPos) { + locateTask = AsyncLocator.locateLevel(level, ImmutableSet.of(StructureFeature.OCEAN_RUIN, StructureFeature.SHIPWRECK), blockPos, 50, false) + .thenOnServerThread(pos -> handleLocationFound(level, pos)); + } + + private void handleLocationFound(ServerLevel level, BlockPos pos) { + locateTask = null; + if (pos != null) { + ModernFix.LOGGER.debug("Location found - updating dolphin treasure pos"); + dolphin.setTreasurePos(pos); + level.broadcastEntityEvent(dolphin, (byte) 38); + } else { + ModernFix.LOGGER.debug("No location found - marking dolphin as stuck"); + stuck = true; + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EnderEyeItemMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EnderEyeItemMixin.java new file mode 100644 index 00000000..1ff2310d --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EnderEyeItemMixin.java @@ -0,0 +1,104 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.advancements.critereon.UsedEnderEyeTrigger; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.Stat; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.EyeOfEnder; +import net.minecraft.world.item.EnderEyeItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import net.minecraft.world.phys.HitResult; +import org.embeddedt.modernfix.structure.logic.EnderEyeItemLogic; +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.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(EnderEyeItem.class) +public class EnderEyeItemMixin { + /* + Intercept EnderEyeItem#use call and return BlockPos.ZERO instead. It won't be used in the EyeOfEnder entity + created later either, as we need to set the actual location ourselves. + */ + @Redirect( + method = "use", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/chunk/ChunkGenerator;findNearestMapFeature(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;" + ) + ) + public BlockPos levelFindNearestMapFeature( + ChunkGenerator generator, + ServerLevel level, + StructureFeature structureFeature, + BlockPos pPos, + int pRadius, + boolean pSkipExistingChunks + ) { + return BlockPos.ZERO; + } + + // Start the async locate task here so we have the eye of ender entity for context + @Inject( + method = "use", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/projectile/EyeOfEnder;setItem(Lnet/minecraft/world/item/ItemStack;)V" + ), + locals = LocalCapture.CAPTURE_FAILEXCEPTION + ) + public void startAsyncLocateTask( + Level pLevel, + Player pPlayer, + InteractionHand pHand, + CallbackInfoReturnable> cir, + ItemStack itemstack, + HitResult hitresult, + BlockPos blockpos, + EyeOfEnder eyeofender + ) { + EnderEyeItemLogic.locateAsync((ServerLevel)pLevel, pPlayer, eyeofender, (EnderEyeItem) (Object) this); + } + + @Redirect( + method = "use", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/projectile/EyeOfEnder;signalTo(Lnet/minecraft/core/BlockPos;)V" + ) + ) + public void eyeOfEnderSignalTo(EyeOfEnder eyeOfEnder, BlockPos blockpos) { + // Do nothing - we'll do this later if a location is found + } + + @Redirect( + method = "use", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/advancements/critereon/UsedEnderEyeTrigger;trigger(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/core/BlockPos;)V" + ) + ) + public void triggerUsedEnderEyeCriteria(UsedEnderEyeTrigger trigger, ServerPlayer player, BlockPos pos) { + // Do nothing - we'll do this later if a location is found + } + + @Redirect( + method = "use", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/stats/Stat;)V" + ) + ) + public void playerAwardStat(Player instance, Stat pStat) { + // Do nothing - we'll do this later if a location is found + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/ExplorationMapFunctionMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/ExplorationMapFunctionMixin.java new file mode 100644 index 00000000..3412fffe --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/ExplorationMapFunctionMixin.java @@ -0,0 +1,63 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import net.minecraft.world.level.saveddata.maps.MapDecoration; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.functions.ExplorationMapFunction; +import net.minecraft.world.phys.Vec3; +import org.embeddedt.modernfix.structure.logic.ExplorationMapFunctionLogic; +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.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ExplorationMapFunction.class) +public class ExplorationMapFunctionMixin { + @Shadow + @Final + StructureFeature destination; + + @Shadow + @Final + MapDecoration.Type mapDecoration; + + @Shadow + @Final + byte zoom; + + @Shadow + @Final + int searchRadius; + + @Shadow + @Final + boolean skipKnownStructures; + + @Inject( + method = "run", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;" + ), + locals = LocalCapture.CAPTURE_FAILSOFT, + cancellable = true + ) + public void updateMapAsync( + ItemStack pStack, + LootContext pContext, + CallbackInfoReturnable cir, + Vec3 vec3, + ServerLevel serverlevel + ) { + ItemStack mapStack = ExplorationMapFunctionLogic.updateMapAsync( + serverlevel, new BlockPos(vec3), zoom, searchRadius, skipKnownStructures, mapDecoration, destination + ); + cir.setReturnValue(mapStack); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EyeOfEnderAccess.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EyeOfEnderAccess.java new file mode 100644 index 00000000..ee8e8861 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EyeOfEnderAccess.java @@ -0,0 +1,11 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.world.entity.projectile.EyeOfEnder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(EyeOfEnder.class) +public interface EyeOfEnderAccess { + @Accessor + void setLife(int life); +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EyeOfEnderMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EyeOfEnderMixin.java new file mode 100644 index 00000000..0b3eadf8 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/EyeOfEnderMixin.java @@ -0,0 +1,37 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.world.entity.projectile.EyeOfEnder; +import org.embeddedt.modernfix.structure.logic.EyeOfEnderData; +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.CallbackInfo; + +@Mixin(EyeOfEnder.class) +public class EyeOfEnderMixin implements EyeOfEnderData { + private boolean locateTaskOngoing = false; + + @Override + public void setLocateTaskOngoing(boolean locateTaskOngoing) { + this.locateTaskOngoing = locateTaskOngoing; + } + + /* + Intercept EyeOfEnder#tick call and return after the super call if there's an ongoing locate task. This is to + prevent the entity from moving or dying until we have a location result. + */ + @Inject( + method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/Entity;tick()V", + shift = At.Shift.AFTER + ), + cancellable = true + ) + public void skipTick(CallbackInfo ci) { + if (locateTaskOngoing) { + ci.cancel(); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/LocateCommandAccess.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/LocateCommandAccess.java new file mode 100644 index 00000000..997c3e70 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/LocateCommandAccess.java @@ -0,0 +1,15 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.minecraft.server.commands.LocateCommand; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(LocateCommand.class) +public interface LocateCommandAccess { + @Accessor("ERROR_FAILED") + static SimpleCommandExceptionType getErrorFailed() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/LocateCommandMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/LocateCommandMixin.java new file mode 100644 index 00000000..d02bddf6 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/LocateCommandMixin.java @@ -0,0 +1,34 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.commands.LocateCommand; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import org.embeddedt.modernfix.structure.logic.LocateCommandLogic; +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; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(LocateCommand.class) +public class LocateCommandMixin { + @Inject( + method = "locate", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;" + ), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILSOFT + ) + private static void findLocationAsync(CommandSourceStack sourceStack, StructureFeature feature, CallbackInfoReturnable cir) { + CommandSource source = ((CommandSourceStackAccess) sourceStack).getSource(); + if (source instanceof ServerPlayer || source instanceof MinecraftServer) { + LocateCommandLogic.locateAsync(sourceStack, feature); + cir.setReturnValue(0); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/MapItemAccess.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/MapItemAccess.java new file mode 100644 index 00000000..890b90b6 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/MapItemAccess.java @@ -0,0 +1,17 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.MapItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(MapItem.class) +public interface MapItemAccess { + @Invoker + static MapItemSavedData callCreateAndStoreSavedData(ItemStack pStack, Level pLevel, int pX, int pZ, int pScale, boolean pTrackingPosition, boolean pUnlimitedTracking, ResourceKey pDimension) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/MerchantOfferAccess.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/MerchantOfferAccess.java new file mode 100644 index 00000000..d9d5bfb2 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/MerchantOfferAccess.java @@ -0,0 +1,13 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.world.item.trading.MerchantOffer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MerchantOffer.class) +public interface MerchantOfferAccess { + @Mutable + @Accessor + void setMaxUses(int maxUses); +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/TreasureMapForEmeraldsMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/TreasureMapForEmeraldsMixin.java new file mode 100644 index 00000000..9ed5b23a --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_locator/TreasureMapForEmeraldsMixin.java @@ -0,0 +1,62 @@ +package org.embeddedt.modernfix.mixin.perf.async_locator; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import net.minecraft.world.level.saveddata.maps.MapDecoration; +import org.embeddedt.modernfix.structure.logic.MerchantLogic; +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.CallbackInfoReturnable; + +import java.util.Locale; +import java.util.Random; + +@Mixin(targets = "net.minecraft.world.entity.npc.VillagerTrades$TreasureMapForEmeralds") +public class TreasureMapForEmeraldsMixin { + @Shadow + @Final + private int emeraldCost; + + @Shadow + @Final + private MapDecoration.Type destinationType; + + @Shadow + @Final + private int maxUses; + + @Shadow + @Final + private int villagerXp; + + @Shadow + @Final + private StructureFeature destination; + + /* + Intercept TreasureMapForEmeralds#getOffer call right before it calls ServerLevel#findNearestMapFeature to pass + the logic over to an async task. Instead of returning the complete map or null, we'll have to always return an + incomplete filled map and later update it with the details when we have them. + */ + @Inject( + method = "getOffer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;" + ), + cancellable = true + ) + public void updateMapAsync(Entity pTrader, Random pRand, CallbackInfoReturnable callbackInfo) { + String displayName = "filled_map." + this.destination.getFeatureName().toLowerCase(Locale.ROOT); + MerchantOffer offer = MerchantLogic.updateMapAsync( + pTrader, emeraldCost, displayName, destinationType, maxUses, villagerXp, destination + ); + if (offer != null) { + callbackInfo.setReturnValue(offer); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/AsyncLocator.java b/src/main/java/org/embeddedt/modernfix/structure/AsyncLocator.java new file mode 100644 index 00000000..3cf025d0 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/AsyncLocator.java @@ -0,0 +1,215 @@ +package org.embeddedt.modernfix.structure; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.thread.SidedThreadGroups; +import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent; +import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; +import org.embeddedt.modernfix.ModernFix; +import org.jetbrains.annotations.NotNull; + +import java.sql.Struct; +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@Mod.EventBusSubscriber(modid = ModernFix.MODID) +public class AsyncLocator { + private static ExecutorService LOCATING_EXECUTOR_SERVICE = null; + private static final AtomicInteger poolNum = new AtomicInteger(1); + + private AsyncLocator() {} + + private static void setupExecutorService() { + shutdownExecutorService(); + + int threads = 1; // very unlikely we need more than one + ModernFix.LOGGER.info("Starting locating executor service with thread pool size of {}", threads); + LOCATING_EXECUTOR_SERVICE = Executors.newFixedThreadPool( + threads, + new ThreadFactory() { + + private final AtomicInteger threadNum = new AtomicInteger(1); + private final String namePrefix = "asynclocator-" + poolNum.getAndIncrement() + "-thread-"; + + @Override + public Thread newThread(@NotNull Runnable r) { + return new Thread(SidedThreadGroups.SERVER, r, namePrefix + threadNum.getAndIncrement()); + } + } + ); + } + + private static void shutdownExecutorService() { + if (LOCATING_EXECUTOR_SERVICE != null) { + ModernFix.LOGGER.info("Shutting down locating executor service"); + LOCATING_EXECUTOR_SERVICE.shutdown(); + } + } + + @SubscribeEvent + public static void handleServerAboutToStartEvent(FMLServerAboutToStartEvent ignoredEvent) { + setupExecutorService(); + } + + @SubscribeEvent + public static void handleServerStoppingEvent(FMLServerStoppingEvent ignoredEvent) { + shutdownExecutorService(); + } + + /** + * Queues a task to locate a feature using {@link ServerLevel#findNearestMapFeature(TagKey, BlockPos, int, boolean)} + * and returns a {@link LocateTask} with the futures for it. + */ + public static LocateTask locateLevel( + ServerLevel level, + Collection> structure, + BlockPos pos, + int searchRadius, + boolean skipKnownStructures + ) { + ModernFix.LOGGER.debug( + "Creating locate task for {} in {} around {} within {} chunks", + structure, level, pos, searchRadius + ); + CompletableFuture completableFuture = new CompletableFuture<>(); + Future future = LOCATING_EXECUTOR_SERVICE.submit( + () -> doLocateLevel(completableFuture, level, structure, pos, searchRadius, skipKnownStructures) + ); + return new LocateTask<>(level.getServer(), completableFuture, future); + } + + /** + * Queues a task to locate a feature using + * {@link ChunkGenerator#findNearestMapFeature(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a + * {@link LocateTask} with the futures for it. + */ + public static LocateTask>> locateChunkGen( + ServerLevel level, + Collection> structureSet, + BlockPos pos, + int searchRadius, + boolean skipKnownStructures + ) { + ModernFix.LOGGER.debug( + "Creating locate task for {} in {} around {} within {} chunks", + structureSet, level, pos, searchRadius + ); + CompletableFuture>> completableFuture = new CompletableFuture<>(); + Future future = LOCATING_EXECUTOR_SERVICE.submit( + () -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures) + ); + return new LocateTask<>(level.getServer(), completableFuture, future); + } + + private static String structureSetToString(Collection> collection) { + return "[" + collection.stream().map(StructureFeature::getRegistryName).map(ResourceLocation::toString).collect(Collectors.joining(", ")) + "]"; + } + + private static void doLocateLevel( + CompletableFuture completableFuture, + ServerLevel level, + Collection> structureTag, + BlockPos pos, + int searchRadius, + boolean skipExistingChunks + ) { + String structures = structureSetToString(structureTag); + ModernFix.LOGGER.debug( + "Trying to locate {} in {} around {} within {} chunks", + structures, level, pos, searchRadius + ); + Optional thePosition = structureTag.stream() + .map(tag -> level.findNearestMapFeature(tag, pos, searchRadius, skipExistingChunks)) + .filter(Objects::nonNull) + .findFirst(); + if (!thePosition.isPresent()) + ModernFix.LOGGER.debug("No {} found", structures); + else + ModernFix.LOGGER.debug("Found {} at {}", structures, thePosition.get()); + completableFuture.complete(thePosition.orElse(null)); + } + + @SuppressWarnings({"rawtypes", "unchecked" }) + private static void doLocateChunkGenerator( + CompletableFuture>> completableFuture, + ServerLevel level, + Collection> structureSet, + BlockPos pos, + int searchRadius, + boolean skipExistingChunks + ) { + String structures = structureSetToString(structureSet); + ModernFix.LOGGER.debug( + "Trying to locate {} in {} around {} within {} chunks", + structures, level, pos, searchRadius + ); + Optional> foundStructure = structureSet.stream() + .map(feature -> Pair.of(level.getChunkSource().getGenerator() + .findNearestMapFeature(level, feature, pos, searchRadius, skipExistingChunks), (StructureFeature)feature)) + .filter(pair -> pair.getFirst() != null) + .findFirst(); + if (!foundStructure.isPresent()) + ModernFix.LOGGER.debug("No {} found", structures); + else + ModernFix.LOGGER.debug("Found {} at {}", structures, foundStructure.get().getFirst()); + completableFuture.complete((Pair>)(Object)foundStructure.orElse(null)); + } + + /** + * Holder of the futures for an async locate task as well as providing some helper functions. + * The completableFuture will be completed once the call to + * {@link ServerLevel#findNearestMapFeature(TagKey, BlockPos, int, boolean)} has completed, and will hold the + * result of it. + * The taskFuture is the future for the {@link Runnable} itself in the executor service. + */ + public static class LocateTask { + private final MinecraftServer server; + private final CompletableFuture completableFuture; + private final Future taskFuture; + public LocateTask(MinecraftServer server, CompletableFuture completableFuture, Future taskFuture) { + this.server = server; + this.completableFuture = completableFuture; + this.taskFuture = taskFuture; + } + /** + * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action. + * Bear in mind that the action will be executed from the task's thread. If you intend to change any game data, + * it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed + * on the main server thread instead. + */ + public LocateTask then(Consumer action) { + completableFuture.thenAccept(action); + return this; + } + + /** + * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server + * thread. + */ + public LocateTask thenOnServerThread(Consumer action) { + completableFuture.thenAccept(pos -> server.submit(() -> action.accept(pos))); + return this; + } + + /** + * Helper function that cancels both completableFuture and taskFuture. + */ + public void cancel() { + taskFuture.cancel(true); + completableFuture.cancel(false); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/logic/CommonLogic.java b/src/main/java/org/embeddedt/modernfix/structure/logic/CommonLogic.java new file mode 100644 index 00000000..a8d0e438 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/logic/CommonLogic.java @@ -0,0 +1,92 @@ +package org.embeddedt.modernfix.structure.logic; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.MapItem; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.saveddata.maps.MapDecoration; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import org.embeddedt.modernfix.mixin.perf.async_locator.MapItemAccess; + +public class CommonLogic { + private CommonLogic() {} + + /** + * Creates an empty "Filled Map", with a hover tooltip name stating that it's locating a feature. + * + * @return The ItemStack + */ + public static ItemStack createEmptyMap() { + ItemStack stack = new ItemStack(Items.FILLED_MAP); + stack.setHoverName(new TranslatableComponent("asynclocator.map.locating")); + return stack; + } + + /** + * Updates the map stack with all the given data. + * + * @param mapStack The map ItemStack to update + * @param level The ServerLevel + * @param pos The feature position + * @param scale The map scale + * @param destinationType The map feature type + */ + public static void updateMap( + ItemStack mapStack, + ServerLevel level, + BlockPos pos, + int scale, + MapDecoration.Type destinationType + ) { + updateMap(mapStack, level, pos, scale, destinationType, null); + } + + /** + * Updates the map stack with all the given data. + * + * @param mapStack The map ItemStack to update + * @param level The ServerLevel + * @param pos The feature position + * @param scale The map scale + * @param destinationType The map feature type + * @param displayName The hover tooltip display name of the ItemStack + */ + public static void updateMap( + ItemStack mapStack, + ServerLevel level, + BlockPos pos, + int scale, + MapDecoration.Type destinationType, + String displayName + ) { + MapItemAccess.callCreateAndStoreSavedData( + mapStack, level, pos.getX(), pos.getZ(), scale, true, true, level.dimension() + ); + MapItem.renderBiomePreviewMap(level, mapStack); + MapItemSavedData.addTargetDecoration(mapStack, pos, "+", destinationType); + if (displayName != null) + mapStack.setHoverName(new TranslatableComponent(displayName)); + } + + /** + * Broadcasts slot changes to all players that have the chest container open. + * Won't do anything if the BlockEntity isn't an instance of {@link ChestBlockEntity}. + */ + public static void broadcastChestChanges(ServerLevel level, BlockEntity be) { + if (!(be instanceof ChestBlockEntity)) + return; + + level.players().forEach(player -> { + AbstractContainerMenu container = player.containerMenu; + if (container instanceof ChestMenu && ((ChestMenu)container).getContainer() == be) { + container.broadcastChanges(); + } + }); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/logic/EnderEyeItemLogic.java b/src/main/java/org/embeddedt/modernfix/structure/logic/EnderEyeItemLogic.java new file mode 100644 index 00000000..d60fce6f --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/logic/EnderEyeItemLogic.java @@ -0,0 +1,38 @@ +package org.embeddedt.modernfix.structure.logic; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.Stats; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.EyeOfEnder; +import net.minecraft.world.item.EnderEyeItem; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import org.embeddedt.modernfix.mixin.perf.async_locator.EyeOfEnderAccess; +import org.embeddedt.modernfix.structure.AsyncLocator; + +public class EnderEyeItemLogic { + private EnderEyeItemLogic() {} + + public static void locateAsync(ServerLevel level, Player player, EyeOfEnder eyeOfEnder, EnderEyeItem enderEyeItem) { + AsyncLocator.locateChunkGen( + level, + ImmutableSet.of(StructureFeature.STRONGHOLD), + player.blockPosition(), + 100, + false + ).thenOnServerThread(pos -> { + ((EyeOfEnderData) eyeOfEnder).setLocateTaskOngoing(false); + if (pos != null) { + eyeOfEnder.signalTo(pos.getFirst()); + CriteriaTriggers.USED_ENDER_EYE.trigger((ServerPlayer) player, pos.getFirst()); + player.awardStat(Stats.ITEM_USED.get(enderEyeItem)); + } else { + // Set the entity's life to long enough that it dies + ((EyeOfEnderAccess) eyeOfEnder).setLife(Integer.MAX_VALUE - 100); + } + }); + ((EyeOfEnderData) eyeOfEnder).setLocateTaskOngoing(true); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/logic/ExplorationMapFunctionLogic.java b/src/main/java/org/embeddedt/modernfix/structure/logic/ExplorationMapFunctionLogic.java new file mode 100644 index 00000000..9a946946 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/logic/ExplorationMapFunctionLogic.java @@ -0,0 +1,111 @@ +package org.embeddedt.modernfix.structure.logic; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import net.minecraft.world.level.saveddata.maps.MapDecoration; +import net.minecraftforge.fml.loading.Java9BackportUtils; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.IItemHandlerModifiable; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.structure.AsyncLocator; + +import java.util.function.BiConsumer; + +// TODO: Need to test this +public class ExplorationMapFunctionLogic { + private static final int MAX_STACK_SIZE = 64; + private ExplorationMapFunctionLogic() {} + + public static void invalidateMap(ItemStack mapStack, ServerLevel level, BlockPos pos) { + handleUpdateMapInChest(mapStack, level, pos, (handler, slot) -> { + if (handler instanceof IItemHandlerModifiable) { + ((IItemHandlerModifiable)handler).setStackInSlot(slot, new ItemStack(Items.MAP)); + } else { + handler.extractItem(slot, MAX_STACK_SIZE, false); + handler.insertItem(slot, new ItemStack(Items.MAP), false); + } + }); + } + + public static void updateMap( + ItemStack mapStack, + ServerLevel level, + BlockPos pos, + int scale, + MapDecoration.Type destinationType, + BlockPos invPos + ) { + CommonLogic.updateMap(mapStack, level, pos, scale, destinationType); + // Shouldn't need to set the stack in its slot again, as we're modifying the same instance + handleUpdateMapInChest(mapStack, level, invPos, (handler, slot) -> {}); + } + + public static void handleUpdateMapInChest( + ItemStack mapStack, + ServerLevel level, + BlockPos invPos, + BiConsumer handleSlotFound + ) { + BlockEntity be = level.getBlockEntity(invPos); + if (be != null) { + Java9BackportUtils.ifPresentOrElse(be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).resolve(), + itemHandler -> { + for (int i = 0; i < itemHandler.getSlots(); i++) { + ItemStack slotStack = itemHandler.getStackInSlot(i); + if (slotStack == mapStack) { + handleSlotFound.accept(itemHandler, i); + CommonLogic.broadcastChestChanges(level, be); + return; + } + } + }, + () -> ModernFix.LOGGER.warn( + "Couldn't find item handler capability on chest {} at {}", + be.getClass().getSimpleName(), invPos + ) + ); + } else { + ModernFix.LOGGER.warn( + "Couldn't find block entity on chest {} at {}", + level.getBlockState(invPos), invPos + ); + } + } + + public static void handleLocationFound( + ItemStack mapStack, + ServerLevel level, + BlockPos pos, + int scale, + MapDecoration.Type destinationType, + BlockPos invPos + ) { + if (pos == null) { + invalidateMap(mapStack, level, invPos); + } else { + updateMap(mapStack, level, pos, scale, destinationType, invPos); + } + } + + public static ItemStack updateMapAsync( + ServerLevel level, + BlockPos blockPos, + int scale, + int searchRadius, + boolean skipKnownStructures, + MapDecoration.Type destinationType, + StructureFeature destination + ) { + ItemStack mapStack = CommonLogic.createEmptyMap(); + AsyncLocator.locateLevel(level, ImmutableSet.of(destination), blockPos, searchRadius, skipKnownStructures) + .thenOnServerThread(pos -> handleLocationFound(mapStack, level, pos, scale, destinationType, blockPos)); + return mapStack; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/logic/EyeOfEnderData.java b/src/main/java/org/embeddedt/modernfix/structure/logic/EyeOfEnderData.java new file mode 100644 index 00000000..1853d3e3 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/logic/EyeOfEnderData.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.structure.logic; + +public interface EyeOfEnderData { + void setLocateTaskOngoing(boolean locateTaskOngoing); +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/logic/LocateCommandLogic.java b/src/main/java/org/embeddedt/modernfix/structure/logic/LocateCommandLogic.java new file mode 100644 index 00000000..173d8d75 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/logic/LocateCommandLogic.java @@ -0,0 +1,30 @@ +package org.embeddedt.modernfix.structure.logic; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.server.commands.LocateCommand; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import org.embeddedt.modernfix.mixin.perf.async_locator.LocateCommandAccess; +import org.embeddedt.modernfix.structure.AsyncLocator; + +public class LocateCommandLogic { + private LocateCommandLogic() {} + + public static void locateAsync(CommandSourceStack sourceStack, StructureFeature feature) { + BlockPos originPos = new BlockPos(sourceStack.getPosition()); + AsyncLocator.locateLevel(sourceStack.getLevel(), ImmutableSet.of(feature), originPos, 100, false) + .thenOnServerThread(pair -> { + if (pair != null) { + LocateCommand.showLocateResult(sourceStack, feature.getFeatureName(), originPos, pair, "commands.locate.success"); + } else { + sourceStack.sendFailure( + new TextComponent( + LocateCommandAccess.getErrorFailed().create().getMessage() + ) + ); + } + }); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/logic/MerchantLogic.java b/src/main/java/org/embeddedt/modernfix/structure/logic/MerchantLogic.java new file mode 100644 index 00000000..5f9ecd35 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/logic/MerchantLogic.java @@ -0,0 +1,125 @@ +package org.embeddedt.modernfix.structure.logic; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.npc.AbstractVillager; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.trading.MerchantOffer; +import net.minecraft.world.level.levelgen.feature.StructureFeature; +import net.minecraft.world.level.saveddata.maps.MapDecoration; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.mixin.perf.async_locator.MerchantOfferAccess; +import org.embeddedt.modernfix.structure.AsyncLocator; + +import java.util.Optional; + +public class MerchantLogic { + private static final boolean REMOVE_OFFER = false; + private MerchantLogic() {} + + public static void invalidateMap(AbstractVillager merchant, ItemStack mapStack) { + mapStack.setHoverName(new TranslatableComponent("asynclocator.map.none")); + Optional offerOptional = merchant.getOffers() + .stream() + .filter(offer -> offer.getResult() == mapStack) + .findFirst(); + if(offerOptional.isPresent()) { + removeOffer(merchant, offerOptional.get()); + } else { + ModernFix.LOGGER.warn("Failed to find merchant offer for map"); + } + } + + public static void removeOffer(AbstractVillager merchant, MerchantOffer offer) { + if (REMOVE_OFFER) { + merchant.getOffers().remove(offer); + } else { + ((MerchantOfferAccess) offer).setMaxUses(0); + offer.setToOutOfStock(); + } + } + + public static void handleLocationFound( + ServerLevel level, + AbstractVillager merchant, + ItemStack mapStack, + String displayName, + MapDecoration.Type destinationType, + BlockPos pos + ) { + if (pos == null) { + invalidateMap(merchant, mapStack); + } else { + CommonLogic.updateMap(mapStack, level, pos, 2, destinationType, displayName); + } + + if (merchant.getTradingPlayer() instanceof ServerPlayer) { + ServerPlayer tradingPlayer = (ServerPlayer)merchant.getTradingPlayer(); + tradingPlayer.sendMerchantOffers( + tradingPlayer.containerMenu.containerId, + merchant.getOffers(), + merchant instanceof Villager ? ((Villager)merchant).getVillagerData().getLevel() : 1, + merchant.getVillagerXp(), + merchant.showProgressBar(), + merchant.canRestock() + ); + } + } + + public static MerchantOffer updateMapAsync( + Entity pTrader, + int emeraldCost, + String displayName, + MapDecoration.Type destinationType, + int maxUses, + int villagerXp, + StructureFeature destination + ) { + return updateMapAsyncInternal( + pTrader, + emeraldCost, + maxUses, + villagerXp, + (level, merchant, mapStack) -> AsyncLocator.locateLevel(level, ImmutableSet.of(destination), merchant.blockPosition(), 100, true) + .thenOnServerThread(pos -> handleLocationFound( + level, + merchant, + mapStack, + displayName, + destinationType, + pos + )) + ); + } + + private static MerchantOffer updateMapAsyncInternal( + Entity trader, int emeraldCost, int maxUses, int villagerXp, MapUpdateTask task + ) { + if (trader instanceof AbstractVillager) { + AbstractVillager merchant = (AbstractVillager)trader; + ItemStack mapStack = CommonLogic.createEmptyMap(); + task.apply((ServerLevel) trader.level, merchant, mapStack); + + return new MerchantOffer( + new ItemStack(Items.EMERALD, emeraldCost), + new ItemStack(Items.COMPASS), + mapStack, + maxUses, + villagerXp, + 0.2F + ); + } else { + return null; + } + } + + public interface MapUpdateTask { + void apply(ServerLevel level, AbstractVillager merchant, ItemStack mapStack); + } +} diff --git a/src/main/resources/assets/modernfix/lang/en_us.json b/src/main/resources/assets/modernfix/lang/en_us.json index de5ec29a..d12ff605 100644 --- a/src/main/resources/assets/modernfix/lang/en_us.json +++ b/src/main/resources/assets/modernfix/lang/en_us.json @@ -1,3 +1,5 @@ { - "modernfix.jei_load": "Loading JEI..." + "modernfix.jei_load": "Loading JEI...", + "asynclocator.map.locating": "Map (Locating...)", + "asynclocator.map.none": "Map (No nearby feature found)" } \ No newline at end of file diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 80dd81d8..036f121a 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -27,6 +27,17 @@ "perf.cache_blockstate_cache_arrays.AbstractBlockStateCacheMixin", "perf.datapack_reload_exceptions.LootTableManagerMixin", "perf.datapack_reload_exceptions.RecipeManagerMixin", + "perf.async_locator.CommandSourceStackAccess", + "perf.async_locator.DolphinSwimToTreasureGoalMixin", + "perf.async_locator.EnderEyeItemMixin", + "perf.async_locator.ExplorationMapFunctionMixin", + "perf.async_locator.EyeOfEnderAccess", + "perf.async_locator.EyeOfEnderMixin", + "perf.async_locator.LocateCommandAccess", + "perf.async_locator.LocateCommandMixin", + "perf.async_locator.MapItemAccess", + "perf.async_locator.MerchantOfferAccess", + "perf.async_locator.TreasureMapForEmeraldsMixin", "feature.measure_time.BootstrapMixin" ], "client": [