Start implementing the forge-mod

This commit is contained in:
Blue (Lukas Rieger) 2020-04-04 16:05:56 +02:00
parent 1ddc229047
commit d172d0f27d
7 changed files with 404 additions and 19 deletions

View File

@ -35,16 +35,24 @@ build.dependsOn shadowJar {
relocate 'com.typesafe', 'de.bluecolored.bluemap.typesafe' relocate 'com.typesafe', 'de.bluecolored.bluemap.typesafe'
relocate 'net.querz', 'de.bluecolored.bluemap.querz' relocate 'net.querz', 'de.bluecolored.bluemap.querz'
relocate 'ninja', 'de.bluecolored.bluemap.ninja' 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' relocate 'org.yaml', 'de.bluecolored.bluemap.yaml'
} }
processResources { processResources {
from(sourceSets.main.resources.srcDirs) { from(sourceSets.main.resources.srcDirs) {
include 'mcmod.info' include 'mcmod.info','META-INF/mods.toml'
expand ( expand (
version: project.version version: project.version
) )
} }
} }
afterEvaluate {
reobf {
shadowJar {
mappings = createMcpToSrg.output
}
}
}

View File

@ -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);
}
}

View File

@ -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<CommandSource> dispatcher) {
LiteralArgumentBuilder<CommandSource> 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<CommandSource> 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<CommandSource> 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<CommandSource> literal(String name){
return LiteralArgumentBuilder.<CommandSource>literal(name);
}
public static <S extends CommandSource, T> RequiredArgumentBuilder<S, T> argument(String name, ArgumentType<T> type){
return RequiredArgumentBuilder.<S, T>argument(name, type);
}
}

View File

@ -12,8 +12,6 @@
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger; 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.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
@ -26,13 +24,15 @@ public class ForgeMod implements ServerInterface {
private Plugin bluemap; private Plugin bluemap;
private MinecraftServer server;
private Map<String, UUID> worldUUIDs; private Map<String, UUID> worldUUIDs;
private ForgeCommands commands;
public ForgeMod() { public ForgeMod() {
Logger.global = new Log4jLogger(LogManager.getLogger()); Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
this.bluemap = new Plugin("forge", this); this.bluemap = new Plugin("forge", this);
this.commands = new ForgeCommands(this, bluemap);
this.worldUUIDs = new HashMap<>(); this.worldUUIDs = new HashMap<>();
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
@ -40,10 +40,15 @@ public ForgeMod() {
@SubscribeEvent @SubscribeEvent
public void onServerStarting(FMLServerStartingEvent event) { public void onServerStarting(FMLServerStartingEvent event) {
this.server = event.getServer();
this.worldUUIDs.clear(); this.worldUUIDs.clear();
for (ServerWorld world : event.getServer().getWorlds()) { for (ServerWorld world : event.getServer().getWorlds()) {
try {
registerWorld(world);
} catch (IOException e) {
Logger.global.logError("Failed to register world: " + world.getProviderName(), e);
}
try { try {
world.save(null, false, false); world.save(null, false, false);
} catch (Throwable t) { } catch (Throwable t) {
@ -51,6 +56,8 @@ public void onServerStarting(FMLServerStartingEvent event) {
} }
} }
this.commands.registerCommands(event.getCommandDispatcher());
new Thread(() -> { new Thread(() -> {
try { try {
Logger.global.logInfo("Loading..."); Logger.global.logInfo("Loading...");
@ -62,6 +69,10 @@ public void onServerStarting(FMLServerStartingEvent event) {
}).start(); }).start();
} }
private void registerWorld(ServerWorld world) throws IOException {
getUUIDForWorld(world);
}
@SubscribeEvent @SubscribeEvent
public void onServerStopping(FMLServerStoppingEvent event) { public void onServerStopping(FMLServerStoppingEvent event) {
Logger.global.logInfo("Stopping..."); Logger.global.logInfo("Stopping...");
@ -83,20 +94,21 @@ public void unregisterAllListeners() {
@Override @Override
public UUID getUUIDForWorld(File worldFolder) throws IOException { public UUID getUUIDForWorld(File worldFolder) throws IOException {
synchronized (worldUUIDs) {
String key = worldFolder.getCanonicalPath();
worldFolder = worldFolder.getCanonicalFile();
UUID uuid = worldUUIDs.get(key);
for (ServerWorld world : server.getWorlds()) { if (uuid == null) {
if (worldFolder.equals(world.getSaveHandler().getWorldDirectory().getCanonicalFile())) return getUUIDForWorld(world); 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) { synchronized (worldUUIDs) {
String key = world.getWorldInfo().getWorldName(); String key = getFolderForWorld(world).getPath();
UUID uuid = worldUUIDs.get(key); UUID uuid = worldUUIDs.get(key);
if (uuid == null) { if (uuid == null) {
@ -107,11 +119,21 @@ public UUID getUUIDForWorld(World world) {
return uuid; 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 @Override
public File getConfigFolder() { public File getConfigFolder() {
//TODO return new File("config/bluemap");
return new File(server.getDataDirectory(), "config");
} }
} }

View File

@ -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"

View File

@ -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
}

View File

@ -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: <data>/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 "<webroot>/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
}
]