/* * * 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 dispatcher) { dispatcher.register(command()); } private static LiteralArgumentBuilder 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 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 = 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 context) throws CommandSyntaxException { context.getSource().sendFeedback( new StringTextComponent("Exporting data"), true ); DataGen.dumpData(); return Command.SINGLE_SUCCESS; } private static int setDefaults(CommandContext 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 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 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 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 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 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 getContext(CommandContext 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 context) { return context.getSource().getWorld().getChunkProvider().getChunkGenerator(); } private static TerraBiomeProvider getBiomeProvider(CommandContext 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"))) ); } }