319 lines
15 KiB
Java
319 lines
15 KiB
Java
/*
|
|
*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2020 TerraForged
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
package com.terraforged.mod.server.command;
|
|
|
|
import com.mojang.brigadier.Command;
|
|
import com.mojang.brigadier.CommandDispatcher;
|
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|
import com.mojang.brigadier.context.CommandContext;
|
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
|
import com.terraforged.core.cell.Cell;
|
|
import com.terraforged.core.concurrent.Resource;
|
|
import com.terraforged.mod.Log;
|
|
import com.terraforged.mod.biome.provider.TerraBiomeProvider;
|
|
import com.terraforged.mod.chunk.TerraChunkGenerator;
|
|
import com.terraforged.mod.chunk.TerraContext;
|
|
import com.terraforged.mod.chunk.settings.SettingsHelper;
|
|
import com.terraforged.mod.data.DataGen;
|
|
import com.terraforged.mod.server.command.arg.BiomeArgType;
|
|
import com.terraforged.mod.server.command.arg.TerrainArgType;
|
|
import com.terraforged.mod.server.command.search.BiomeSearchTask;
|
|
import com.terraforged.mod.server.command.search.BothSearchTask;
|
|
import com.terraforged.mod.server.command.search.Search;
|
|
import com.terraforged.mod.server.command.search.TerrainSearchTask;
|
|
import com.terraforged.world.WorldGenerator;
|
|
import com.terraforged.world.terrain.Terrain;
|
|
import com.terraforged.world.terrain.Terrains;
|
|
import net.minecraft.command.CommandSource;
|
|
import net.minecraft.command.Commands;
|
|
import net.minecraft.command.arguments.ArgumentSerializer;
|
|
import net.minecraft.command.arguments.ArgumentTypes;
|
|
import net.minecraft.entity.player.PlayerEntity;
|
|
import net.minecraft.entity.player.ServerPlayerEntity;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.util.math.BlockPos;
|
|
import net.minecraft.util.text.ITextComponent;
|
|
import net.minecraft.util.text.StringTextComponent;
|
|
import net.minecraft.util.text.TextComponentUtils;
|
|
import net.minecraft.util.text.TextFormatting;
|
|
import net.minecraft.util.text.TranslationTextComponent;
|
|
import net.minecraft.util.text.event.ClickEvent;
|
|
import net.minecraft.util.text.event.HoverEvent;
|
|
import net.minecraft.world.biome.Biome;
|
|
import net.minecraft.world.biome.ColumnFuzzedBiomeMagnifier;
|
|
import net.minecraft.world.dimension.DimensionType;
|
|
import net.minecraft.world.gen.ChunkGenerator;
|
|
import net.minecraft.world.server.ServerWorld;
|
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
|
import net.minecraftforge.fml.common.Mod;
|
|
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
|
|
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.function.Supplier;
|
|
|
|
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE)
|
|
public class TerraCommand {
|
|
|
|
public static void init() {
|
|
ArgumentTypes.register("terraforged:biome", BiomeArgType.class, new ArgumentSerializer<>(BiomeArgType::new));
|
|
ArgumentTypes.register("terraforged:terrain", TerrainArgType.class, new ArgumentSerializer<>(TerrainArgType::new));
|
|
}
|
|
|
|
@SubscribeEvent
|
|
public static void register(FMLServerStartingEvent event) {
|
|
Log.info("Registering /terra command");
|
|
register(event.getCommandDispatcher());
|
|
}
|
|
|
|
public static void register(CommandDispatcher<CommandSource> dispatcher) {
|
|
dispatcher.register(command());
|
|
}
|
|
|
|
private static LiteralArgumentBuilder<CommandSource> command() {
|
|
return Commands.literal("terra")
|
|
.requires(source -> source.hasPermissionLevel(2))
|
|
.then(Commands.literal("query")
|
|
.executes(TerraCommand::query))
|
|
.then(Commands.literal("data")
|
|
.then(Commands.literal("dump")
|
|
.executes(TerraCommand::dump)))
|
|
.then(Commands.literal("defaults")
|
|
.then(Commands.literal("set")
|
|
.executes(TerraCommand::setDefaults)))
|
|
.then(Commands.literal("debug")
|
|
.executes(TerraCommand::debugBiome))
|
|
.then(Commands.literal("locate")
|
|
.then(Commands.literal("biome")
|
|
.then(Commands.argument("biome", BiomeArgType.biome())
|
|
.executes(TerraCommand::findBiome)))
|
|
.then(Commands.literal("terrain")
|
|
.then(Commands.argument("terrain", TerrainArgType.terrain())
|
|
.executes(TerraCommand::findTerrain)))
|
|
.then(Commands.literal("both")
|
|
.then(Commands.argument("biome", BiomeArgType.biome())
|
|
.then(Commands.argument("terrain", TerrainArgType.terrain())
|
|
.executes(TerraCommand::findTerrainAndBiome)))));
|
|
}
|
|
|
|
private static int query(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
getContext(context).orElseThrow(() -> createException(
|
|
"Invalid world type",
|
|
"This command can only be run in a TerraForged world!"
|
|
));
|
|
|
|
BlockPos pos = context.getSource().asPlayer().getPosition();
|
|
TerraBiomeProvider biomeProvider = getBiomeProvider(context);
|
|
try (Resource<Cell> cell = biomeProvider.lookupPos(pos.getX(), pos.getZ())) {
|
|
Biome biome = biomeProvider.getBiome(cell.get(), pos.getX(), pos.getZ());
|
|
context.getSource().sendFeedback(
|
|
new StringTextComponent(
|
|
"Terrain=" + cell.get().terrain.getName()
|
|
+ ", Biome=" + biome.getRegistryName()
|
|
+ ", BiomeType=" + cell.get().biomeType.name()
|
|
),
|
|
false
|
|
);
|
|
}
|
|
|
|
return Command.SINGLE_SUCCESS;
|
|
}
|
|
|
|
private static int dump(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
context.getSource().sendFeedback(
|
|
new StringTextComponent("Exporting data"),
|
|
true
|
|
);
|
|
DataGen.dumpData();
|
|
return Command.SINGLE_SUCCESS;
|
|
}
|
|
|
|
private static int setDefaults(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
TerraContext terraContext = getContext(context).orElseThrow(() -> createException(
|
|
"Invalid world type",
|
|
"This command can only be run in a TerraForged world!"
|
|
));
|
|
|
|
context.getSource().sendFeedback(
|
|
new StringTextComponent("Setting generator defaults"),
|
|
true
|
|
);
|
|
SettingsHelper.exportDefaults(terraContext.terraSettings);
|
|
return Command.SINGLE_SUCCESS;
|
|
}
|
|
|
|
private static int debugBiome(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
ServerPlayerEntity player = context.getSource().asPlayer();
|
|
BlockPos position = player.getPosition();
|
|
int x = position.getX();
|
|
int z = position.getZ();
|
|
|
|
long seed = player.getServerWorld().getSeed();
|
|
Biome actual = player.getServerWorld().getBiome(position);
|
|
Biome biome2 = ColumnFuzzedBiomeMagnifier.INSTANCE.getBiome(seed, x, 0, z, player.getServerWorld().getWorldServer().getChunkProvider().generator.getBiomeProvider());
|
|
|
|
context.getSource().sendFeedback(new StringTextComponent(
|
|
"Actual Biome = " + actual.getRegistryName()
|
|
+ "\nLookup Biome = " + biome2.getRegistryName()),
|
|
false
|
|
);
|
|
|
|
return Command.SINGLE_SUCCESS;
|
|
}
|
|
|
|
private static int findTerrain(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
// get the generator's context
|
|
TerraContext terraContext = getContext(context).orElseThrow(() -> createException(
|
|
"Invalid world type",
|
|
"This command can only be run in a TerraForged world!"
|
|
));
|
|
|
|
Terrain terrain = TerrainArgType.getTerrain(context, "terrain");
|
|
Terrain type = getTerrainInstance(terrain, terraContext.terrain);
|
|
BlockPos pos = context.getSource().asPlayer().getPosition();
|
|
UUID playerID = context.getSource().asPlayer().getUniqueID();
|
|
MinecraftServer server = context.getSource().getServer();
|
|
WorldGenerator generator = terraContext.factory.get();
|
|
Search search = new TerrainSearchTask(pos, type, getChunkGenerator(context), generator);
|
|
doSearch(server, playerID, search);
|
|
context.getSource().sendFeedback(new StringTextComponent("Searching..."), false);
|
|
|
|
return Command.SINGLE_SUCCESS;
|
|
}
|
|
|
|
private static int findBiome(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
// get the generator's context
|
|
getContext(context).orElseThrow(() -> createException(
|
|
"Invalid world type",
|
|
"This command can only be run in a TerraForged world!"
|
|
));
|
|
|
|
Biome biome = BiomeArgType.getBiome(context, "biome");
|
|
BlockPos pos = context.getSource().asPlayer().getPosition();
|
|
UUID playerID = context.getSource().asPlayer().getUniqueID();
|
|
MinecraftServer server = context.getSource().getServer();
|
|
ServerWorld world = context.getSource().asPlayer().getServerWorld();
|
|
Search search = new BiomeSearchTask(pos, biome, world.getChunkProvider().getChunkGenerator(), getBiomeProvider(context));
|
|
doSearch(server, playerID, search);
|
|
context.getSource().sendFeedback(new StringTextComponent("Searching..."), false);
|
|
|
|
return Command.SINGLE_SUCCESS;
|
|
}
|
|
|
|
private static int findTerrainAndBiome(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
// get the generator's context
|
|
TerraContext terraContext = getContext(context).orElseThrow(() -> createException(
|
|
"Invalid world type",
|
|
"This command can only be run in a TerraForged world!"
|
|
));
|
|
|
|
Terrain terrain = TerrainArgType.getTerrain(context, "terrain");
|
|
Terrain target = getTerrainInstance(terrain, terraContext.terrain);
|
|
Biome biome = BiomeArgType.getBiome(context, "biome");
|
|
BlockPos pos = context.getSource().asPlayer().getPosition();
|
|
UUID playerID = context.getSource().asPlayer().getUniqueID();
|
|
MinecraftServer server = context.getSource().getServer();
|
|
WorldGenerator generator = terraContext.factory.get();
|
|
Search biomeSearch = new BiomeSearchTask(pos, biome, getChunkGenerator(context), getBiomeProvider(context));
|
|
Search terrainSearch = new TerrainSearchTask(pos, target, getChunkGenerator(context), generator);
|
|
Search search = new BothSearchTask(pos, biomeSearch, terrainSearch);
|
|
doSearch(server, playerID, search);
|
|
context.getSource().sendFeedback(new StringTextComponent("Searching..."), false);
|
|
|
|
return Command.SINGLE_SUCCESS;
|
|
}
|
|
|
|
private static void doSearch(MinecraftServer server, UUID userId, Supplier<BlockPos> supplier) {
|
|
CompletableFuture.supplyAsync(supplier).thenAccept(pos -> server.deferTask(() -> {
|
|
PlayerEntity player = server.getPlayerList().getPlayerByUUID(userId);
|
|
if (player == null) {
|
|
return;
|
|
}
|
|
|
|
if (pos.getX() == 0 && pos.getZ() == 0) {
|
|
player.sendMessage(new StringTextComponent("Location not found :["));
|
|
return;
|
|
}
|
|
|
|
double distance = Math.sqrt(player.getPosition().distanceSq(pos));
|
|
|
|
ITextComponent result = new StringTextComponent("Nearest match: ")
|
|
.appendSibling(createTeleportMessage(pos))
|
|
.appendSibling(new StringTextComponent(String.format(" Distance: %.2f", distance)));
|
|
|
|
player.sendMessage(result);
|
|
}));
|
|
}
|
|
|
|
private static Optional<TerraContext> getContext(CommandContext<CommandSource> context) throws CommandSyntaxException {
|
|
MinecraftServer server = context.getSource().getServer();
|
|
DimensionType dimension = context.getSource().asPlayer().dimension;
|
|
ChunkGenerator<?> generator = server.getWorld(dimension).getChunkProvider().getChunkGenerator();
|
|
if (generator instanceof TerraChunkGenerator) {
|
|
TerraChunkGenerator gen = (TerraChunkGenerator) generator;
|
|
return Optional.of(gen.getContext());
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
// the terrain parsed from the command will not be the same instance as used in the
|
|
// world generator, so find the matching instance by name
|
|
private static Terrain getTerrainInstance(Terrain find, Terrains terrains) {
|
|
for (Terrain t : terrains.index) {
|
|
if (t.getName().equals(find.getName())) {
|
|
return t;
|
|
}
|
|
}
|
|
return find;
|
|
}
|
|
|
|
private static ChunkGenerator<?> getChunkGenerator(CommandContext<CommandSource> context) {
|
|
return context.getSource().getWorld().getChunkProvider().getChunkGenerator();
|
|
}
|
|
|
|
private static TerraBiomeProvider getBiomeProvider(CommandContext<CommandSource> context) {
|
|
return (TerraBiomeProvider) context.getSource().getWorld().getChunkProvider().getChunkGenerator().getBiomeProvider();
|
|
}
|
|
|
|
private static CommandSyntaxException createException(String type, String message, Object... args) {
|
|
return new CommandSyntaxException(
|
|
new SimpleCommandExceptionType(new StringTextComponent(type)),
|
|
new StringTextComponent(String.format(message, args))
|
|
);
|
|
}
|
|
|
|
private static ITextComponent createTeleportMessage(BlockPos pos) {
|
|
return TextComponentUtils.wrapInSquareBrackets(new TranslationTextComponent(
|
|
"chat.coordinates", pos.getX(), "~", pos.getZ()
|
|
)).applyTextStyle((style) -> style.setColor(TextFormatting.GREEN)
|
|
.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/tp @s " + pos.getX() + " " + pos.getY() + " " + pos.getZ()))
|
|
.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslationTextComponent("chat.coordinates.tooltip")))
|
|
);
|
|
}
|
|
}
|