Backport Async Locator
This commit is contained in:
parent
4e3b839f36
commit
8f971a2c84
|
|
@ -9,12 +9,14 @@ import net.minecraftforge.fml.ModLoadingContext;
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import net.minecraftforge.fml.config.ModConfig;
|
import net.minecraftforge.fml.config.ModConfig;
|
||||||
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
|
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
|
||||||
|
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
|
||||||
import net.minecraftforge.fml.loading.FMLLoader;
|
import net.minecraftforge.fml.loading.FMLLoader;
|
||||||
import net.minecraftforge.fml.network.FMLNetworkConstants;
|
import net.minecraftforge.fml.network.FMLNetworkConstants;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.embeddedt.modernfix.core.config.ModernFixConfig;
|
import org.embeddedt.modernfix.core.config.ModernFixConfig;
|
||||||
|
import org.embeddedt.modernfix.structure.AsyncLocator;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ public class ModernFixEarlyConfig {
|
||||||
this.addMixinRule("perf.faster_baking", true);
|
this.addMixinRule("perf.faster_baking", true);
|
||||||
this.addMixinRule("perf.cache_model_materials", true);
|
this.addMixinRule("perf.cache_model_materials", true);
|
||||||
this.addMixinRule("perf.datapack_reload_exceptions", 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 */
|
/* 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("perf.blast_search_trees", FMLLoader.getLoadingModList().getModFileById("jei") != null);
|
||||||
this.addMixinRule("safety", true);
|
this.addMixinRule("safety", true);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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<BlockPos> 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<Boolean> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<InteractionResultHolder<ItemStack>> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<ItemStack> cir,
|
||||||
|
Vec3 vec3,
|
||||||
|
ServerLevel serverlevel
|
||||||
|
) {
|
||||||
|
ItemStack mapStack = ExplorationMapFunctionLogic.updateMapAsync(
|
||||||
|
serverlevel, new BlockPos(vec3), zoom, searchRadius, skipKnownStructures, mapDecoration, destination
|
||||||
|
);
|
||||||
|
cir.setReturnValue(mapStack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Integer> cir) {
|
||||||
|
CommandSource source = ((CommandSourceStackAccess) sourceStack).getSource();
|
||||||
|
if (source instanceof ServerPlayer || source instanceof MinecraftServer) {
|
||||||
|
LocateCommandLogic.locateAsync(sourceStack, feature);
|
||||||
|
cir.setReturnValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Level> pDimension) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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<MerchantOffer> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<BlockPos> locateLevel(
|
||||||
|
ServerLevel level,
|
||||||
|
Collection<StructureFeature<?>> structure,
|
||||||
|
BlockPos pos,
|
||||||
|
int searchRadius,
|
||||||
|
boolean skipKnownStructures
|
||||||
|
) {
|
||||||
|
ModernFix.LOGGER.debug(
|
||||||
|
"Creating locate task for {} in {} around {} within {} chunks",
|
||||||
|
structure, level, pos, searchRadius
|
||||||
|
);
|
||||||
|
CompletableFuture<BlockPos> 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<Pair<BlockPos, StructureFeature<?>>> locateChunkGen(
|
||||||
|
ServerLevel level,
|
||||||
|
Collection<StructureFeature<?>> structureSet,
|
||||||
|
BlockPos pos,
|
||||||
|
int searchRadius,
|
||||||
|
boolean skipKnownStructures
|
||||||
|
) {
|
||||||
|
ModernFix.LOGGER.debug(
|
||||||
|
"Creating locate task for {} in {} around {} within {} chunks",
|
||||||
|
structureSet, level, pos, searchRadius
|
||||||
|
);
|
||||||
|
CompletableFuture<Pair<BlockPos, StructureFeature<?>>> 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<StructureFeature<?>> collection) {
|
||||||
|
return "[" + collection.stream().map(StructureFeature::getRegistryName).map(ResourceLocation::toString).collect(Collectors.joining(", ")) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void doLocateLevel(
|
||||||
|
CompletableFuture<BlockPos> completableFuture,
|
||||||
|
ServerLevel level,
|
||||||
|
Collection<StructureFeature<?>> 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<BlockPos> 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<Pair<BlockPos, StructureFeature<?>>> completableFuture,
|
||||||
|
ServerLevel level,
|
||||||
|
Collection<StructureFeature<?>> 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<Pair<BlockPos, StructureFeature>> 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<BlockPos, StructureFeature<?>>)(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<T> {
|
||||||
|
private final MinecraftServer server;
|
||||||
|
private final CompletableFuture<T> completableFuture;
|
||||||
|
private final Future<?> taskFuture;
|
||||||
|
public LocateTask(MinecraftServer server, CompletableFuture<T> 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<T> then(Consumer<T> action) {
|
||||||
|
completableFuture.thenAccept(action);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server
|
||||||
|
* thread.
|
||||||
|
*/
|
||||||
|
public LocateTask<T> thenOnServerThread(Consumer<T> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<IItemHandler, Integer> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.embeddedt.modernfix.structure.logic;
|
||||||
|
|
||||||
|
public interface EyeOfEnderData {
|
||||||
|
void setLocateTaskOngoing(boolean locateTaskOngoing);
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<MerchantOffer> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)"
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +27,17 @@
|
||||||
"perf.cache_blockstate_cache_arrays.AbstractBlockStateCacheMixin",
|
"perf.cache_blockstate_cache_arrays.AbstractBlockStateCacheMixin",
|
||||||
"perf.datapack_reload_exceptions.LootTableManagerMixin",
|
"perf.datapack_reload_exceptions.LootTableManagerMixin",
|
||||||
"perf.datapack_reload_exceptions.RecipeManagerMixin",
|
"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"
|
"feature.measure_time.BootstrapMixin"
|
||||||
],
|
],
|
||||||
"client": [
|
"client": [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user