mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2025-02-04 14:41:23 +01:00
Merge branch 'mc/1.13'
This commit is contained in:
commit
23528da625
@ -73,7 +73,7 @@ public boolean execute(CommandSender sender, CommandSource source, String[] args
|
||||
}
|
||||
});
|
||||
|
||||
commands.add(new Command("bluemap.rendertask.create.world", "render") {
|
||||
commands.add(new Command("bluemap.render", "render") {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, CommandSource source, String[] args) {
|
||||
if (sender instanceof Player) {
|
||||
@ -113,7 +113,7 @@ public boolean execute(CommandSender sender, CommandSource source, String[] args
|
||||
}
|
||||
});
|
||||
|
||||
commands.add(new Command("bluemap.rendertask.prioritize", "render", "prioritize") {
|
||||
commands.add(new Command("bluemap.render", "render", "prioritize") {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, CommandSource source, String[] args) {
|
||||
if (args.length != 1) return false;
|
||||
@ -129,7 +129,7 @@ public boolean execute(CommandSender sender, CommandSource source, String[] args
|
||||
}
|
||||
});
|
||||
|
||||
commands.add(new Command("bluemap.rendertask.remove", "render", "remove") {
|
||||
commands.add(new Command("bluemap.render", "render", "remove") {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, CommandSource source, String[] args) {
|
||||
if (args.length != 1) return false;
|
||||
|
@ -23,9 +23,7 @@ permissions:
|
||||
bluemap.reload: true
|
||||
bluemap.pause: true
|
||||
bluemap.resume: true
|
||||
bluemap.rendertask.create.world: true
|
||||
bluemap.rendertask.prioritize: true
|
||||
bluemap.rendertask.remove: true
|
||||
bluemap.render: true
|
||||
bluemap.debug: true
|
||||
default: op
|
||||
bluemap.status:
|
||||
@ -36,11 +34,7 @@ permissions:
|
||||
default: op
|
||||
bluemap.resume:
|
||||
default: op
|
||||
bluemap.rendertask.create.world:
|
||||
default: op
|
||||
bluemap.rendertask.prioritize:
|
||||
default: op
|
||||
bluemap.rendertask.remove:
|
||||
bluemap.render:
|
||||
default: op
|
||||
bluemap.debug:
|
||||
default: op
|
@ -155,6 +155,58 @@ public boolean executeResumeCommand(CommandSource source) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command: /bluemap render [world,map]
|
||||
*/
|
||||
public boolean executeRenderCommand(CommandSource source, String mapOrWorld) {
|
||||
return executeRenderCommand(source, mapOrWorld, null, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command: /bluemap render [world,map] [block-radius]
|
||||
*/
|
||||
public boolean executeRenderCommand(CommandSource source, String mapOrWorld, Vector2i center, int blockRadius) {
|
||||
if (!checkLoaded(source)) return false;
|
||||
|
||||
MapType map = null;
|
||||
World world = null;
|
||||
for (MapType m : bluemap.getMapTypes()) {
|
||||
if (mapOrWorld.equalsIgnoreCase(m.getId()) || mapOrWorld.equalsIgnoreCase(m.getName())){
|
||||
map = m;
|
||||
world = map.getWorld();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (world == null) {
|
||||
for (World w : bluemap.getWorlds()) {
|
||||
if (mapOrWorld.equalsIgnoreCase(w.getName())){
|
||||
world = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (world == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "Could not find a world or map with this name or id ", TextColor.GRAY, "('" + mapOrWorld + "')", TextColor.RED, "! Maybe it is not configured in BlueMap's config?"));
|
||||
}
|
||||
|
||||
world.invalidateChunkCache();
|
||||
|
||||
final World worldToRender = world;
|
||||
final MapType mapToRender = map;
|
||||
if (mapToRender == null) {
|
||||
new Thread(() -> {
|
||||
createWorldRenderTask(source, worldToRender, center, blockRadius);
|
||||
}).start();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
createMapRenderTask(source, mapToRender, center, blockRadius);
|
||||
}).start();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command: /bluemap render [world]
|
||||
@ -172,7 +224,7 @@ public boolean executeRenderWorldCommand(CommandSource source, UUID worldUuid, V
|
||||
World world = bluemap.getWorld(worldUuid);
|
||||
|
||||
if (world == null) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "This world is not loaded with BlueMap! Maybe it is not configured?"));
|
||||
source.sendMessage(Text.of(TextColor.RED, "This world is not loaded with BlueMap! Maybe it is not configured in BlueMap's config?"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -307,6 +359,36 @@ private void createWorldRenderTask(CommandSource source, World world, Vector2i c
|
||||
source.sendMessage(Text.of(TextColor.GREEN, "All render tasks created! Use /bluemap to view the progress!"));
|
||||
}
|
||||
|
||||
private void createMapRenderTask(CommandSource source, MapType map, Vector2i center, long blockRadius) {
|
||||
source.sendMessage(Text.of(TextColor.GOLD, "Collecting chunks to render..."));
|
||||
|
||||
String taskName = "world-render";
|
||||
|
||||
Predicate<Vector2i> filter;
|
||||
if (center == null || blockRadius < 0) {
|
||||
filter = c -> true;
|
||||
} else {
|
||||
filter = c -> c.mul(16).distanceSquared(center) <= blockRadius * blockRadius;
|
||||
taskName = "radius-render";
|
||||
}
|
||||
|
||||
Collection<Vector2i> chunks = map.getWorld().getChunkList(filter);
|
||||
|
||||
source.sendMessage(Text.of(TextColor.GREEN, chunks.size() + " chunks found!"));
|
||||
source.sendMessage(Text.of(TextColor.GOLD, "Collecting tiles for map '" + map.getId() + "'"));
|
||||
|
||||
HiresModelManager hmm = map.getTileRenderer().getHiresModelManager();
|
||||
Collection<Vector2i> tiles = hmm.getTilesForChunks(chunks);
|
||||
|
||||
RenderTask task = new RenderTask(taskName, map);
|
||||
task.addTiles(tiles);
|
||||
task.optimizeQueue();
|
||||
bluemap.getRenderManager().addRenderTask(task);
|
||||
|
||||
source.sendMessage(Text.of(TextColor.GREEN, tiles.size() + " tiles found! Task created."));
|
||||
source.sendMessage(Text.of(TextColor.GREEN, "All render tasks created! Use /bluemap to view the progress!"));
|
||||
}
|
||||
|
||||
private boolean checkLoaded(CommandSource source) {
|
||||
if (!bluemap.isLoaded()) {
|
||||
source.sendMessage(Text.of(TextColor.RED, "BlueMap is not loaded!", TextColor.GRAY, "(Try /bluemap reload)"));
|
||||
|
@ -361,6 +361,10 @@ public World getWorld(UUID uuid){
|
||||
return worlds.get(uuid);
|
||||
}
|
||||
|
||||
public Collection<World> getWorlds(){
|
||||
return worlds.values();
|
||||
}
|
||||
|
||||
public Collection<MapType> getMapTypes(){
|
||||
return maps.values();
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
package de.bluecolored.bluemap.forge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.LiteralMessage;
|
||||
@ -9,9 +14,7 @@
|
||||
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;
|
||||
@ -19,64 +22,70 @@
|
||||
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.Entity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.server.permission.DefaultPermissionLevel;
|
||||
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");
|
||||
|
||||
|
||||
PermissionAPI.registerNode("bluemap.status", DefaultPermissionLevel.OP, "Permission for using /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 -> {
|
||||
|
||||
PermissionAPI.registerNode("bluemap.reload", DefaultPermissionLevel.OP, "Permission for using /bluemap reload");
|
||||
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 -> {
|
||||
}));
|
||||
|
||||
PermissionAPI.registerNode("bluemap.pause", DefaultPermissionLevel.OP, "Permission for using /bluemap pause");
|
||||
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 -> {
|
||||
}));
|
||||
|
||||
PermissionAPI.registerNode("bluemap.resume", DefaultPermissionLevel.OP, "Permission for using /bluemap resume");
|
||||
base.then(literal("resume").executes(c -> {
|
||||
if (!checkPermission(c, "bluemap.resume")) return 0;
|
||||
|
||||
commands.executeResumeCommand(new ForgeCommandSource(c.getSource()));
|
||||
return 1;
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
PermissionAPI.registerNode("bluemap.render", DefaultPermissionLevel.OP, "Permission for using /bluemap render");
|
||||
Command<CommandSource> renderCommand = c -> {
|
||||
if (!checkPermission(c, "bluemap.render")) return 0;
|
||||
|
||||
String worldName = null;
|
||||
try {
|
||||
c.getArgument("world", String.class);
|
||||
worldName = c.getArgument("world", String.class);
|
||||
} catch (IllegalArgumentException ex) {}
|
||||
|
||||
int blockRadius = -1;
|
||||
try {
|
||||
c.getArgument("block-radius", Integer.class);
|
||||
blockRadius = c.getArgument("block-radius", Integer.class);
|
||||
} catch (IllegalArgumentException ex) {}
|
||||
|
||||
PlayerEntity player = null;
|
||||
@ -84,19 +93,84 @@ public void registerCommands(CommandDispatcher<CommandSource> dispatcher) {
|
||||
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 {
|
||||
|
||||
if (player == null && blockRadius != -1) {
|
||||
throw new SimpleCommandExceptionType(new LiteralMessage("You can only use a block-radius if you are a player!")).create();
|
||||
}
|
||||
|
||||
if (worldName == null) {
|
||||
if (player == null) throw new SimpleCommandExceptionType(new LiteralMessage("You need to define a world! (/bluemap render <world>)")).create();
|
||||
Vector2i center = new Vector2i(player.getPosition().getX(), player.getPosition().getZ());
|
||||
|
||||
UUID world;
|
||||
try {
|
||||
world = mod.getUUIDForWorld((ServerWorld) player.getEntityWorld());
|
||||
} catch (IOException ex) {
|
||||
throw new SimpleCommandExceptionType(new LiteralMessage("Could not detect the world you are currently in, try to define a world using /bluemap render <world>")).create();
|
||||
}
|
||||
|
||||
return commands.executeRenderWorldCommand(new ForgeCommandSource(c.getSource()), world, center, blockRadius) ? 1 : 0;
|
||||
} else {
|
||||
if (player == null) {
|
||||
return commands.executeRenderCommand(new ForgeCommandSource(c.getSource()), worldName) ? 1 : 0;
|
||||
} else {
|
||||
Vector2i center = new Vector2i(player.getPosition().getX(), player.getPosition().getZ());
|
||||
return commands.executeRenderCommand(new ForgeCommandSource(c.getSource()), worldName, center, blockRadius) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
base.then(literal("render")
|
||||
.executes(renderCommand)
|
||||
.then(argument("block-radius", IntegerArgumentType.integer(0)).executes(renderCommand))
|
||||
.then(argument("world", StringArgumentType.word())
|
||||
.executes(renderCommand)
|
||||
.then(argument("block-radius", IntegerArgumentType.integer(0))).executes(renderCommand)
|
||||
)
|
||||
|
||||
.then(literal("prioritize").then(argument("task-uuid", StringArgumentType.word()).executes(c -> {
|
||||
if (!checkPermission(c, "bluemap.render")) return 0;
|
||||
|
||||
try {
|
||||
UUID taskUUID = UUID.fromString(c.getArgument("task-uuid", String.class));
|
||||
commands.executePrioritizeRenderTaskCommand(new ForgeCommandSource(c.getSource()), taskUUID);
|
||||
return 1;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw new SimpleCommandExceptionType(new LiteralMessage("Invalid task-uuid!")).create();
|
||||
}
|
||||
})))
|
||||
|
||||
.then(literal("remove").then(argument("task-uuid", StringArgumentType.word()).executes(c -> {
|
||||
if (!checkPermission(c, "bluemap.render")) return 0;
|
||||
|
||||
try {
|
||||
UUID taskUUID = UUID.fromString(c.getArgument("task-uuid", String.class));
|
||||
commands.executeRemoveRenderTaskCommand(new ForgeCommandSource(c.getSource()), taskUUID);
|
||||
return 1;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw new SimpleCommandExceptionType(new LiteralMessage("Invalid task-uuid!")).create();
|
||||
}
|
||||
})))
|
||||
);
|
||||
|
||||
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);
|
||||
PermissionAPI.registerNode("bluemap.debug", DefaultPermissionLevel.OP, "Permission for using /bluemap debug");
|
||||
base.then(literal("debug").executes(c -> {
|
||||
if (!checkPermission(c, "bluemap.debug")) return 0;
|
||||
|
||||
Entity entity = c.getSource().assertIsEntity();
|
||||
BlockPos mcPos = entity.getPosition();
|
||||
Vector3i pos = new Vector3i(mcPos.getX(), mcPos.getY(), mcPos.getZ());
|
||||
|
||||
UUID world;
|
||||
try {
|
||||
world = mod.getUUIDForWorld((ServerWorld) entity.getEntityWorld());
|
||||
} catch (IOException e) {
|
||||
throw new SimpleCommandExceptionType(new LiteralMessage("Could not detect the world you are currently in!")).create();
|
||||
}
|
||||
|
||||
commands.executeDebugCommand(new ForgeCommandSource(c.getSource()), world, pos);
|
||||
return 1;
|
||||
}));
|
||||
|
||||
dispatcher.register(base);
|
||||
}
|
||||
@ -110,7 +184,7 @@ private boolean checkPermission(CommandContext<CommandSource> command, String pe
|
||||
hasPermission = true;
|
||||
}
|
||||
} catch (CommandSyntaxException ex) {
|
||||
if (command.getSource().hasPermissionLevel(2)) {
|
||||
if (command.getSource().hasPermissionLevel(1)) {
|
||||
hasPermission = true;
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,24 @@
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
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.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
|
||||
@ -23,10 +29,9 @@
|
||||
public class ForgeMod implements ServerInterface {
|
||||
|
||||
private Plugin bluemap;
|
||||
|
||||
private Map<String, UUID> worldUUIDs;
|
||||
|
||||
private ForgeCommands commands;
|
||||
private Map<String, UUID> worldUUIDs;
|
||||
private Collection<ServerEventListener> eventListeners;
|
||||
|
||||
public ForgeMod() {
|
||||
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
|
||||
@ -34,6 +39,7 @@ public ForgeMod() {
|
||||
this.bluemap = new Plugin("forge", this);
|
||||
this.commands = new ForgeCommands(this, bluemap);
|
||||
this.worldUUIDs = new HashMap<>();
|
||||
this.eventListeners = new ArrayList<>(1);
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
@ -82,14 +88,50 @@ public void onServerStopping(FMLServerStoppingEvent event) {
|
||||
|
||||
@Override
|
||||
public void registerListener(ServerEventListener listener) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
eventListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAllListeners() {
|
||||
// TODO Auto-generated method stub
|
||||
eventListeners.clear();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockBreak(BlockEvent.BreakEvent evt) {
|
||||
onBlockChange(evt);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockPlace(BlockEvent.EntityPlaceEvent evt) {
|
||||
onBlockChange(evt);
|
||||
}
|
||||
|
||||
private void onBlockChange(BlockEvent evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
try {
|
||||
UUID world = getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
Vector3i position = new Vector3i(
|
||||
evt.getPos().getX(),
|
||||
evt.getPos().getY(),
|
||||
evt.getPos().getZ()
|
||||
);
|
||||
|
||||
for (ServerEventListener listener : eventListeners) listener.onBlockChange(world, position);
|
||||
|
||||
} catch (IOException ignore) {}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onWorldSave(WorldEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
try {
|
||||
UUID world = getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
|
||||
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world);
|
||||
|
||||
} catch (IOException ignore) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,7 +81,7 @@ public CommandSpec createResumeRenderCommand() {
|
||||
public CommandSpec createRenderCommand() {
|
||||
return CommandSpec.builder()
|
||||
.description(Text.of("Renders the whole world"))
|
||||
.permission("bluemap.rendertask.create.world")
|
||||
.permission("bluemap.render")
|
||||
.childArgumentParseExceptionFallback(false)
|
||||
.child(createPrioritizeTaskCommand(), "prioritize")
|
||||
.child(createRemoveTaskCommand(), "remove")
|
||||
@ -134,7 +134,7 @@ public CommandSpec createRenderCommand() {
|
||||
public CommandSpec createPrioritizeTaskCommand() {
|
||||
return CommandSpec.builder()
|
||||
.description(Text.of("Prioritizes the render-task with the given uuid"))
|
||||
.permission("bluemap.rendertask.prioritize")
|
||||
.permission("bluemap.render")
|
||||
.arguments(GenericArguments.uuid(Text.of("task-uuid")))
|
||||
.executor((source, args) -> {
|
||||
Optional<UUID> uuid = args.<UUID>getOne("task-uuid");
|
||||
@ -152,7 +152,7 @@ public CommandSpec createPrioritizeTaskCommand() {
|
||||
public CommandSpec createRemoveTaskCommand() {
|
||||
return CommandSpec.builder()
|
||||
.description(Text.of("Removes the render-task with the given uuid"))
|
||||
.permission("bluemap.rendertask.remove")
|
||||
.permission("bluemap.render")
|
||||
.arguments(GenericArguments.uuid(Text.of("task-uuid")))
|
||||
.executor((source, args) -> {
|
||||
Optional<UUID> uuid = args.<UUID>getOne("task-uuid");
|
||||
|
Loading…
Reference in New Issue
Block a user