196 lines
9.1 KiB
Java
196 lines
9.1 KiB
Java
package net.minecraft.server.commands;
|
|
|
|
import com.google.common.base.Stopwatch;
|
|
import com.mojang.brigadier.CommandDispatcher;
|
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
|
import com.mojang.datafixers.util.Pair;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.time.Duration;
|
|
import java.util.Optional;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.commands.CommandBuildContext;
|
|
import net.minecraft.commands.CommandSourceStack;
|
|
import net.minecraft.commands.Commands;
|
|
import net.minecraft.commands.arguments.ResourceOrTagArgument;
|
|
import net.minecraft.commands.arguments.ResourceOrTagKeyArgument;
|
|
import net.minecraft.commands.arguments.ResourceOrTagKeyArgument.Result;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.HolderSet;
|
|
import net.minecraft.core.Registry;
|
|
import net.minecraft.core.HolderSet.ListBacked;
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.network.chat.ClickEvent;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.ComponentUtils;
|
|
import net.minecraft.network.chat.HoverEvent;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
|
import net.minecraft.world.entity.ai.village.poi.PoiManager.Occupancy;
|
|
import net.minecraft.world.level.biome.Biome;
|
|
import net.minecraft.world.level.levelgen.structure.Structure;
|
|
import org.slf4j.Logger;
|
|
|
|
public class LocateCommand {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final DynamicCommandExceptionType ERROR_STRUCTURE_NOT_FOUND = new DynamicCommandExceptionType(
|
|
object -> Component.translatableEscape("commands.locate.structure.not_found", object)
|
|
);
|
|
private static final DynamicCommandExceptionType ERROR_STRUCTURE_INVALID = new DynamicCommandExceptionType(
|
|
object -> Component.translatableEscape("commands.locate.structure.invalid", object)
|
|
);
|
|
private static final DynamicCommandExceptionType ERROR_BIOME_NOT_FOUND = new DynamicCommandExceptionType(
|
|
object -> Component.translatableEscape("commands.locate.biome.not_found", object)
|
|
);
|
|
private static final DynamicCommandExceptionType ERROR_POI_NOT_FOUND = new DynamicCommandExceptionType(
|
|
object -> Component.translatableEscape("commands.locate.poi.not_found", object)
|
|
);
|
|
private static final int MAX_STRUCTURE_SEARCH_RADIUS = 100;
|
|
private static final int MAX_BIOME_SEARCH_RADIUS = 6400;
|
|
private static final int BIOME_SAMPLE_RESOLUTION_HORIZONTAL = 32;
|
|
private static final int BIOME_SAMPLE_RESOLUTION_VERTICAL = 64;
|
|
private static final int POI_SEARCH_RADIUS = 256;
|
|
|
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext context) {
|
|
dispatcher.register(
|
|
Commands.literal("locate")
|
|
.requires(commandSourceStack -> commandSourceStack.hasPermission(2))
|
|
.then(
|
|
Commands.literal("structure")
|
|
.then(
|
|
Commands.argument("structure", ResourceOrTagKeyArgument.resourceOrTagKey(Registries.STRUCTURE))
|
|
.executes(
|
|
commandContext -> locateStructure(
|
|
commandContext.getSource(), ResourceOrTagKeyArgument.getResourceOrTagKey(commandContext, "structure", Registries.STRUCTURE, ERROR_STRUCTURE_INVALID)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
.then(
|
|
Commands.literal("biome")
|
|
.then(
|
|
Commands.argument("biome", ResourceOrTagArgument.resourceOrTag(context, Registries.BIOME))
|
|
.executes(commandContext -> locateBiome(commandContext.getSource(), ResourceOrTagArgument.getResourceOrTag(commandContext, "biome", Registries.BIOME)))
|
|
)
|
|
)
|
|
.then(
|
|
Commands.literal("poi")
|
|
.then(
|
|
Commands.argument("poi", ResourceOrTagArgument.resourceOrTag(context, Registries.POINT_OF_INTEREST_TYPE))
|
|
.executes(
|
|
commandContext -> locatePoi(
|
|
commandContext.getSource(), ResourceOrTagArgument.getResourceOrTag(commandContext, "poi", Registries.POINT_OF_INTEREST_TYPE)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
private static Optional<? extends ListBacked<Structure>> getHolders(Result<Structure> structure, Registry<Structure> structureRegistry) {
|
|
return structure.unwrap().map(resourceKey -> structureRegistry.get(resourceKey).map(holder -> HolderSet.direct(holder)), structureRegistry::get);
|
|
}
|
|
|
|
private static int locateStructure(CommandSourceStack source, Result<Structure> structure) throws CommandSyntaxException {
|
|
Registry<Structure> registry = source.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
|
HolderSet<Structure> holderSet = (HolderSet<Structure>)getHolders(structure, registry)
|
|
.orElseThrow(() -> ERROR_STRUCTURE_INVALID.create(structure.asPrintable()));
|
|
BlockPos blockPos = BlockPos.containing(source.getPosition());
|
|
ServerLevel serverLevel = source.getLevel();
|
|
Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER);
|
|
Pair<BlockPos, Holder<Structure>> pair = serverLevel.getChunkSource().getGenerator().findNearestMapStructure(serverLevel, holderSet, blockPos, 100, false);
|
|
stopwatch.stop();
|
|
if (pair == null) {
|
|
throw ERROR_STRUCTURE_NOT_FOUND.create(structure.asPrintable());
|
|
} else {
|
|
return showLocateResult(source, structure, blockPos, pair, "commands.locate.structure.success", false, stopwatch.elapsed());
|
|
}
|
|
}
|
|
|
|
private static int locateBiome(CommandSourceStack source, net.minecraft.commands.arguments.ResourceOrTagArgument.Result<Biome> biome) throws CommandSyntaxException {
|
|
BlockPos blockPos = BlockPos.containing(source.getPosition());
|
|
Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER);
|
|
Pair<BlockPos, Holder<Biome>> pair = source.getLevel().findClosestBiome3d(biome, blockPos, 6400, 32, 64);
|
|
stopwatch.stop();
|
|
if (pair == null) {
|
|
throw ERROR_BIOME_NOT_FOUND.create(biome.asPrintable());
|
|
} else {
|
|
return showLocateResult(source, biome, blockPos, pair, "commands.locate.biome.success", true, stopwatch.elapsed());
|
|
}
|
|
}
|
|
|
|
private static int locatePoi(CommandSourceStack source, net.minecraft.commands.arguments.ResourceOrTagArgument.Result<PoiType> poiType) throws CommandSyntaxException {
|
|
BlockPos blockPos = BlockPos.containing(source.getPosition());
|
|
ServerLevel serverLevel = source.getLevel();
|
|
Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER);
|
|
Optional<Pair<Holder<PoiType>, BlockPos>> optional = serverLevel.getPoiManager().findClosestWithType(poiType, blockPos, 256, Occupancy.ANY);
|
|
stopwatch.stop();
|
|
if (optional.isEmpty()) {
|
|
throw ERROR_POI_NOT_FOUND.create(poiType.asPrintable());
|
|
} else {
|
|
return showLocateResult(source, poiType, blockPos, ((Pair)optional.get()).swap(), "commands.locate.poi.success", false, stopwatch.elapsed());
|
|
}
|
|
}
|
|
|
|
public static int showLocateResult(
|
|
CommandSourceStack source,
|
|
net.minecraft.commands.arguments.ResourceOrTagArgument.Result<?> result,
|
|
BlockPos sourcePosition,
|
|
Pair<BlockPos, ? extends Holder<?>> resultWithPosition,
|
|
String translationKey,
|
|
boolean absoluteY,
|
|
Duration duration
|
|
) {
|
|
String string = result.unwrap()
|
|
.map(reference -> result.asPrintable(), named -> result.asPrintable() + " (" + resultWithPosition.getSecond().getRegisteredName() + ")");
|
|
return showLocateResult(source, sourcePosition, resultWithPosition, translationKey, absoluteY, string, duration);
|
|
}
|
|
|
|
public static int showLocateResult(
|
|
CommandSourceStack source,
|
|
Result<?> result,
|
|
BlockPos sourcePosition,
|
|
Pair<BlockPos, ? extends Holder<?>> resultWithPosition,
|
|
String translationKey,
|
|
boolean absoluteY,
|
|
Duration duration
|
|
) {
|
|
String string = result.unwrap()
|
|
.map(resourceKey -> resourceKey.location().toString(), tagKey -> "#" + tagKey.location() + " (" + resultWithPosition.getSecond().getRegisteredName() + ")");
|
|
return showLocateResult(source, sourcePosition, resultWithPosition, translationKey, absoluteY, string, duration);
|
|
}
|
|
|
|
private static int showLocateResult(
|
|
CommandSourceStack source,
|
|
BlockPos sourcePosition,
|
|
Pair<BlockPos, ? extends Holder<?>> resultWithoutPosition,
|
|
String translationKey,
|
|
boolean absoluteY,
|
|
String elementName,
|
|
Duration duration
|
|
) {
|
|
BlockPos blockPos = resultWithoutPosition.getFirst();
|
|
int i = absoluteY
|
|
? Mth.floor(Mth.sqrt((float)sourcePosition.distSqr(blockPos)))
|
|
: Mth.floor(dist(sourcePosition.getX(), sourcePosition.getZ(), blockPos.getX(), blockPos.getZ()));
|
|
String string = absoluteY ? String.valueOf(blockPos.getY()) : "~";
|
|
Component component = ComponentUtils.wrapInSquareBrackets(Component.translatable("chat.coordinates", blockPos.getX(), string, blockPos.getZ()))
|
|
.withStyle(
|
|
style -> style.withColor(ChatFormatting.GREEN)
|
|
.withClickEvent(new ClickEvent.SuggestCommand("/tp @s " + blockPos.getX() + " " + string + " " + blockPos.getZ()))
|
|
.withHoverEvent(new HoverEvent.ShowText(Component.translatable("chat.coordinates.tooltip")))
|
|
);
|
|
source.sendSuccess(() -> Component.translatable(translationKey, elementName, component, i), false);
|
|
LOGGER.info("Locating element " + elementName + " took " + duration.toMillis() + " ms");
|
|
return i;
|
|
}
|
|
|
|
private static float dist(int x1, int z1, int x2, int z2) {
|
|
int i = x2 - x1;
|
|
int j = z2 - z1;
|
|
return Mth.sqrt(i * i + j * j);
|
|
}
|
|
}
|