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.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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.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": [
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user