diff --git a/BlueMapForge/build.gradle b/BlueMapForge/build.gradle index 4da5966d..06e426ef 100644 --- a/BlueMapForge/build.gradle +++ b/BlueMapForge/build.gradle @@ -35,16 +35,24 @@ build.dependsOn shadowJar { relocate 'com.typesafe', 'de.bluecolored.bluemap.typesafe' relocate 'net.querz', 'de.bluecolored.bluemap.querz' relocate 'ninja', 'de.bluecolored.bluemap.ninja' - relocate 'org.apache', 'de.bluecolored.bluemap.apache' + relocate 'org.apache.commons', 'de.bluecolored.bluemap.apache.commons' relocate 'org.yaml', 'de.bluecolored.bluemap.yaml' } processResources { from(sourceSets.main.resources.srcDirs) { - include 'mcmod.info' + include 'mcmod.info','META-INF/mods.toml' expand ( version: project.version ) } } + +afterEvaluate { + reobf { + shadowJar { + mappings = createMcpToSrg.output + } + } +} diff --git a/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeCommandSource.java b/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeCommandSource.java new file mode 100644 index 00000000..6d935383 --- /dev/null +++ b/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeCommandSource.java @@ -0,0 +1,20 @@ +package de.bluecolored.bluemap.forge; + +import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; +import de.bluecolored.bluemap.common.plugin.text.Text; +import net.minecraft.util.text.ITextComponent; + +public class ForgeCommandSource implements CommandSource { + + private net.minecraft.command.CommandSource delegate; + + public ForgeCommandSource(net.minecraft.command.CommandSource delegate) { + this.delegate = delegate; + } + + @Override + public void sendMessage(Text text) { + delegate.sendFeedback(ITextComponent.Serializer.fromJson(text.toJSONString()), false); + } + +} diff --git a/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeCommands.java b/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeCommands.java new file mode 100644 index 00000000..a00766c0 --- /dev/null +++ b/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeCommands.java @@ -0,0 +1,133 @@ +package de.bluecolored.bluemap.forge; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandExceptionType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; + +import de.bluecolored.bluemap.common.plugin.Commands; +import de.bluecolored.bluemap.common.plugin.Plugin; +import de.bluecolored.bluemap.common.plugin.text.Text; +import de.bluecolored.bluemap.common.plugin.text.TextColor; +import net.minecraft.command.CommandSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraftforge.server.permission.PermissionAPI; + +public class ForgeCommands { + + private ForgeMod mod; + private Plugin bluemap; + private Commands commands; + + public ForgeCommands(ForgeMod mod, Plugin bluemap) { + this.mod = mod; + this.bluemap = bluemap; + this.commands = bluemap.getCommands(); + } + + public void registerCommands(CommandDispatcher dispatcher) { + + LiteralArgumentBuilder base = literal("bluemap"); + + base.executes(c -> { + if (!checkPermission(c, "bluemap.status")) return 0; + + commands.executeRootCommand(new ForgeCommandSource(c.getSource())); + return 1; + }); + + base.then(literal("reload")).executes(c -> { + if (!checkPermission(c, "bluemap.reload")) return 0; + + commands.executeReloadCommand(new ForgeCommandSource(c.getSource())); + return 1; + }); + + base.then(literal("pause")).executes(c -> { + if (!checkPermission(c, "bluemap.pause")) return 0; + + commands.executePauseCommand(new ForgeCommandSource(c.getSource())); + return 1; + }); + + base.then(literal("resume")).executes(c -> { + if (!checkPermission(c, "bluemap.resume")) return 0; + + commands.executeResumeCommand(new ForgeCommandSource(c.getSource())); + return 1; + }); + + Command renderCommand = c -> { + if (!checkPermission(c, "bluemap.render")) return 0; + + String worldName = null; + try { + c.getArgument("world", String.class); + } catch (IllegalArgumentException ex) {} + + int blockRadius = -1; + try { + c.getArgument("block-radius", Integer.class); + } catch (IllegalArgumentException ex) {} + + PlayerEntity player = null; + try { + player = c.getSource().asPlayer(); + } catch (CommandSyntaxException ex) {} + + if (player == null) { + if (worldName == null) throw new SimpleCommandExceptionType(new LiteralMessage("There is no world with this name: " + worldName)).create(); + } else { + + } + + return 1; + }; + + base.then(literal("render")).executes(renderCommand); + base.then(literal("render")).then(argument("world", StringArgumentType.word())).executes(renderCommand); + base.then(literal("render")).then(argument("block-radius", IntegerArgumentType.integer(0))).executes(renderCommand); + base.then(literal("render")).then(argument("world", StringArgumentType.word())).then(argument("block-radius", IntegerArgumentType.integer(0))).executes(renderCommand); + + dispatcher.register(base); + } + + private boolean checkPermission(CommandContext command, String permission) { + ForgeCommandSource cs = new ForgeCommandSource(command.getSource()); + + boolean hasPermission = false; + try { + if (PermissionAPI.hasPermission(command.getSource().asPlayer(), permission)) { + hasPermission = true; + } + } catch (CommandSyntaxException ex) { + if (command.getSource().hasPermissionLevel(2)) { + hasPermission = true; + } + } + + if (!hasPermission) { + cs.sendMessage(Text.of(TextColor.RED, "You don't have the permissions to use this command!")); + } + + return hasPermission; + } + + public static LiteralArgumentBuilder literal(String name){ + return LiteralArgumentBuilder.literal(name); + } + + public static RequiredArgumentBuilder argument(String name, ArgumentType type){ + return RequiredArgumentBuilder.argument(name, type); + } + +} diff --git a/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index b58797fb..de742d4b 100644 --- a/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/BlueMapForge/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -12,8 +12,6 @@ import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.core.logger.Logger; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.World; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -26,13 +24,15 @@ public class ForgeMod implements ServerInterface { private Plugin bluemap; - private MinecraftServer server; private Map worldUUIDs; + private ForgeCommands commands; + public ForgeMod() { - Logger.global = new Log4jLogger(LogManager.getLogger()); + Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME)); this.bluemap = new Plugin("forge", this); + this.commands = new ForgeCommands(this, bluemap); this.worldUUIDs = new HashMap<>(); MinecraftForge.EVENT_BUS.register(this); @@ -40,10 +40,15 @@ public ForgeMod() { @SubscribeEvent public void onServerStarting(FMLServerStartingEvent event) { - this.server = event.getServer(); this.worldUUIDs.clear(); for (ServerWorld world : event.getServer().getWorlds()) { + try { + registerWorld(world); + } catch (IOException e) { + Logger.global.logError("Failed to register world: " + world.getProviderName(), e); + } + try { world.save(null, false, false); } catch (Throwable t) { @@ -51,6 +56,8 @@ public void onServerStarting(FMLServerStartingEvent event) { } } + this.commands.registerCommands(event.getCommandDispatcher()); + new Thread(() -> { try { Logger.global.logInfo("Loading..."); @@ -62,6 +69,10 @@ public void onServerStarting(FMLServerStartingEvent event) { }).start(); } + private void registerWorld(ServerWorld world) throws IOException { + getUUIDForWorld(world); + } + @SubscribeEvent public void onServerStopping(FMLServerStoppingEvent event) { Logger.global.logInfo("Stopping..."); @@ -83,20 +94,21 @@ public void unregisterAllListeners() { @Override public UUID getUUIDForWorld(File worldFolder) throws IOException { - - - worldFolder = worldFolder.getCanonicalFile(); - - for (ServerWorld world : server.getWorlds()) { - if (worldFolder.equals(world.getSaveHandler().getWorldDirectory().getCanonicalFile())) return getUUIDForWorld(world); + synchronized (worldUUIDs) { + String key = worldFolder.getCanonicalPath(); + + UUID uuid = worldUUIDs.get(key); + if (uuid == null) { + throw new IOException("There is no world with this folder loaded: " + worldFolder.getPath()); + } + + return uuid; } - - throw new IOException("There is no world with this folder loaded: " + worldFolder.getPath()); } - public UUID getUUIDForWorld(World world) { + public UUID getUUIDForWorld(ServerWorld world) throws IOException { synchronized (worldUUIDs) { - String key = world.getWorldInfo().getWorldName(); + String key = getFolderForWorld(world).getPath(); UUID uuid = worldUUIDs.get(key); if (uuid == null) { @@ -107,11 +119,21 @@ public UUID getUUIDForWorld(World world) { return uuid; } } + + private File getFolderForWorld(ServerWorld world) throws IOException { + File worldFolder = world.getSaveHandler().getWorldDirectory(); + + int dimensionId = world.getDimension().getType().getId(); + if (dimensionId != 0) { + worldFolder = new File(worldFolder, "DIM" + dimensionId); + } + + return worldFolder.getCanonicalFile(); + } @Override public File getConfigFolder() { - //TODO - return new File(server.getDataDirectory(), "config"); + return new File("config/bluemap"); } } diff --git a/BlueMapForge/src/main/resources/META-INF/mods.toml b/BlueMapForge/src/main/resources/META-INF/mods.toml new file mode 100644 index 00000000..253c1248 --- /dev/null +++ b/BlueMapForge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,25 @@ +modLoader="javafml" +loaderVersion="[28,)" +issueTrackerURL="https://github.com/BlueMap-Minecraft/BlueMap/issues" +[[mods]] +modId="bluemap" +version="${version}" +displayName="BlueMap" +displayURL="https://github.com/BlueMap-Minecraft/BlueMap" +authors="Blue (TBlueF, Lukas Rieger)" +description=''' +A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL) +''' + +[[dependencies.bluemap]] + modId="forge" + mandatory=true + versionRange="[28,)" + ordering="NONE" + side="SERVER" +[[dependencies.bluemap]] + modId="minecraft" + mandatory=true + versionRange="[1.15.2]" + ordering="NONE" + side="SERVER" \ No newline at end of file diff --git a/BlueMapForge/src/main/resources/bluemap-forge-defaults.conf b/BlueMapForge/src/main/resources/bluemap-forge-defaults.conf new file mode 100644 index 00000000..c6f20621 --- /dev/null +++ b/BlueMapForge/src/main/resources/bluemap-forge-defaults.conf @@ -0,0 +1,11 @@ +accept-download: false +metrics: true +renderThreadCount: -2 +data: "bluemap" +webroot: "bluemap/web" +useCookies: true +webserver { + enabled: true + port: 8100 + maxConnectionCount: 100 +} diff --git a/BlueMapForge/src/main/resources/bluemap-forge.conf b/BlueMapForge/src/main/resources/bluemap-forge.conf new file mode 100644 index 00000000..8b740ce2 --- /dev/null +++ b/BlueMapForge/src/main/resources/bluemap-forge.conf @@ -0,0 +1,166 @@ +## ## +## BlueMap ## +## ## +## by Blue (Lukas Rieger) ## +## http://bluecolored.de/ ## +## ## + +# By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula), +# you confirm that you own a license to Minecraft (Java Edition) +# and you agree that BlueMap will download and use this file for you: %minecraft-client-url% +# (Alternatively you can download the file yourself and store it here: /minecraft-client-%minecraft-client-version%.jar) +# This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compliant with mojang's EULA. +# BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.) +# %datetime-iso% +accept-download: false + +# This changes the amount of threads that BlueMap will use to render the maps. +# A higher value can improve render-speed but could impact performance on the host machine. +# This should be always below or equal to the number of available processor-cores. +# Zero or a negative value means the amount of of available processor-cores subtracted by the value. +# (So a value of -2 with 6 cores results in 4 render-processes) +# Default is -2 +renderThreadCount: -2 + +# If this is true, BlueMap might send really basic metrics reports containg only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/ +# This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :) +# An example report looks like this: {"implementation":"forge","version":"%version%"} +metrics: true + +# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later. +data: "bluemap" + +# The webroot of the website that displays the map. +webroot: "bluemap/web" + +# Unncomment this to override the path where bluemap stores the data-files. +# Default is "/data" +#webdata: "path/to/data/folder" + +# If the web-application should use cookies to save the configurations of a user. +useCookies: true + +webserver { + # With this setting you can disable the integrated web-server. + # This is usefull if you want to only render the map-data for later use, or if you setup your own webserver. + # Default is enabled + enabled: true + + # The IP-Adress that the webserver binds to. + # If this setting is commented out, bluemap tries to find the default ip-adress of your system. + # If you only want to access it locally use "localhost". + #ip: "localhost" + #ip: "127.0.0.1" + + # The port that the webserver listenes to. + # Default is 8100 + port: 8100 + + # Max number of simultaneous connections that the webserver allows + # Default is 100 + maxConnectionCount: 100 +} + +# This is an array with multiple configured maps. +# You can define multiple maps, for different worlds with different render-settings here +maps: [ + + { + # The id of this map + # Should only contain word-charactes: [a-zA-Z0-9_] + # Changing this value breaks your existing renders. + id: "world" + + # The name of this map + # This defines the display name of this map, you can change this at any time. + # Default is the id of this map + name: "World" + + # The path to the save-folder of the world to render. + world: "world" + + # The position on the world where the map will be centered if you open it. + # You can change this at any time. + # This defaults to the world-spawn if you don't set it. + #startPos: [500, -820] + + # The color of thy sky as a hex-color + # You can change this at any time. + # Default is "#7dabff" + skyColor: "#7dabff" + + # Defines the ambient light-strength that every block is recieving, regardless of the sunlight/blocklight. + # 0 is no ambient light, 1 is fully lighted. + # You can change this at any time. + # Default is 0 + ambientLight: 0 + + # If this is false, BlueMap tries to omit all blocks that are not visible from above-ground. + # More specific: Block-Faces that have a sunlight/skylight value of 0 are removed. + # This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible. + # Changing this value requires a re-render of the map. + # Default is false + renderCaves: false + + # With the below values you can limit the map-render. + # This can be used to ignore the nethers ceiling or render only a certain part of a world. + # Changing this values might require a re-render of the map, already rendered tiles outside the limits will not be deleted. + # Default is no min or max value (= infinite bounds) + #minX: -4000 + #maxX: 4000 + #minZ: -4000 + #maxZ: 4000 + #minY: 50 + #maxY: 126 + + # Using this, BlueMap pretends that every Block out of the defined render-bounds is AIR, + # this means you can see the blocks where the world is cut (instead of having a see-through/xray view). + # This has only an effect if you set some render-bounds above. + # Changing this value requires a re-render of the map. + # Default is true + renderEdges: true + + # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. + # Files will be only 5% as big with compression! + # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. + # This is much better than disabling the compression. + # Changing this value requires a re-render of the map. + # Default is true + useCompression: true + } + + # Here another example for the End-Map + # Things we don't want to change from default we can just omit + { + id: "end" + name: "End" + world: "world/DIM1" + + # We dont want a blue sky in the end + skyColor: "#080010" + + # In the end is no sky-light, so we need to enable this or we won't see anything. + renderCaves: true + + # Same here, we don't want a dark map. But not completely lighted, so we see the effect of e.g torches. + ambientLight: 0.6 + } + + # Here another example for the Nether-Map + { + id: "nether" + name: "Nether" + world: "world/DIM-1" + + skyColor: "#290000" + + renderCaves: true + ambientLight: 0.6 + + # We slice the whole world at y:90 so every block above 90 will be air. + # This way we don't render the nethers ceiling. + maxY: 90 + renderEdges: true + } + +]