Things
This commit is contained in:
@@ -1,19 +1,14 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.client.core.VoxelCore;
|
||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||
import me.cortex.voxy.client.terrain.WorldImportCommand;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientWorldEvents;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
|
||||
public class VoxyClient implements ClientModInitializer {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||
dispatcher.register(WorldImportCommand.register());
|
||||
dispatcher.register(VoxyCommands.register());
|
||||
});
|
||||
VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.client.core.WorldImportWrapper;
|
||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldGetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldSetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||
import me.cortex.voxy.commonImpl.ImportManager;
|
||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||
import me.cortex.voxy.commonImpl.importers.DHImporter;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class VoxyClientInstance extends VoxyInstance {
|
||||
private static final ContextSelectionSystem SELECTOR = new ContextSelectionSystem();
|
||||
|
||||
@@ -27,10 +21,10 @@ public class VoxyClientInstance extends VoxyInstance {
|
||||
}
|
||||
|
||||
public WorldEngine getOrMakeRenderWorld(ClientWorld world) {
|
||||
var vworld = ((IVoxyWorldGetter)world).getWorldEngine();
|
||||
var vworld = ((IVoxyWorld)world).getWorldEngine();
|
||||
if (vworld == null) {
|
||||
vworld = this.createWorld(SELECTOR.getBestSelectionOrCreate(world).createSectionStorageBackend());
|
||||
((IVoxyWorldSetter)world).setWorldEngine(vworld);
|
||||
((IVoxyWorld)world).setWorldEngine(vworld);
|
||||
} else {
|
||||
if (!this.activeWorlds.contains(vworld)) {
|
||||
throw new IllegalStateException("World referenced does not exist in instance");
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package me.cortex.voxy.client.terrain;
|
||||
package me.cortex.voxy.client;
|
||||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import me.cortex.voxy.client.VoxyClientInstance;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||
import me.cortex.voxy.commonImpl.importers.DHImporter;
|
||||
import me.cortex.voxy.commonImpl.importers.WorldImporter;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
||||
@@ -22,35 +22,64 @@ import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
||||
public class WorldImportCommand {
|
||||
public class VoxyCommands {
|
||||
|
||||
public static LiteralArgumentBuilder<FabricClientCommandSource> register() {
|
||||
return ClientCommandManager.literal("voxy").requires((ctx)-> VoxyCommon.getInstance() != null)
|
||||
.then(ClientCommandManager.literal("reload")
|
||||
.executes(VoxyCommands::reloadInstance))
|
||||
.then(ClientCommandManager.literal("import")
|
||||
.then(ClientCommandManager.literal("world")
|
||||
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
||||
.suggests(WorldImportCommand::importWorldSuggester)
|
||||
.executes(WorldImportCommand::importWorld)))
|
||||
.suggests(VoxyCommands::importWorldSuggester)
|
||||
.executes(VoxyCommands::importWorld)))
|
||||
.then(ClientCommandManager.literal("bobby")
|
||||
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
||||
.suggests(WorldImportCommand::importBobbySuggester)
|
||||
.executes(WorldImportCommand::importBobby)))
|
||||
.suggests(VoxyCommands::importBobbySuggester)
|
||||
.executes(VoxyCommands::importBobby)))
|
||||
.then(ClientCommandManager.literal("raw")
|
||||
.then(ClientCommandManager.argument("path", StringArgumentType.string())
|
||||
.executes(WorldImportCommand::importRaw)))
|
||||
.executes(VoxyCommands::importRaw)))
|
||||
.then(ClientCommandManager.literal("zip")
|
||||
.then(ClientCommandManager.argument("zipPath", StringArgumentType.string())
|
||||
.executes(WorldImportCommand::importZip)
|
||||
.executes(VoxyCommands::importZip)
|
||||
.then(ClientCommandManager.argument("innerPath", StringArgumentType.string())
|
||||
.executes(WorldImportCommand::importZip))))
|
||||
.executes(VoxyCommands::importZip))))
|
||||
.then(ClientCommandManager.literal("distant_horizons")
|
||||
.then(ClientCommandManager.argument("sqlDbPath", StringArgumentType.string())
|
||||
.executes(WorldImportCommand::importDistantHorizons)))
|
||||
.executes(VoxyCommands::importDistantHorizons)))
|
||||
.then(ClientCommandManager.literal("cancel")
|
||||
.executes(WorldImportCommand::cancelImport))
|
||||
.executes(VoxyCommands::cancelImport))
|
||||
);
|
||||
}
|
||||
|
||||
private static int reloadInstance(CommandContext<FabricClientCommandSource> ctx) {
|
||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
return 1;
|
||||
}
|
||||
var wr = MinecraftClient.getInstance().worldRenderer;
|
||||
if (wr!=null) {
|
||||
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
||||
}
|
||||
var w = ((IVoxyWorld)MinecraftClient.getInstance().world);
|
||||
if (w != null) {
|
||||
if (w.getWorldEngine() != null) {
|
||||
instance.stopWorld(w.getWorldEngine());
|
||||
}
|
||||
w.setWorldEngine(null);
|
||||
}
|
||||
VoxyCommon.shutdownInstance();
|
||||
VoxyCommon.createInstance();
|
||||
if (wr!=null) {
|
||||
((IGetVoxyRenderSystem)wr).createRenderer();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static int importDistantHorizons(CommandContext<FabricClientCommandSource> ctx) {
|
||||
var instance = (VoxyClientInstance)VoxyCommon.getInstance();
|
||||
if (instance == null) {
|
||||
@@ -3,8 +3,7 @@ package me.cortex.voxy.client.config;
|
||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldGetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldSetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import me.shedaniel.clothconfig2.api.ConfigBuilder;
|
||||
import me.shedaniel.clothconfig2.api.ConfigCategory;
|
||||
@@ -40,7 +39,7 @@ public class VoxyConfigScreenFactory implements ModMenuApi {
|
||||
builder.setSavingRunnable(() -> {
|
||||
//After saving the core should be reloaded/reset
|
||||
var worldRenderer = MinecraftClient.getInstance().worldRenderer;
|
||||
var world = MinecraftClient.getInstance().world;
|
||||
var world = ((IVoxyWorld) MinecraftClient.getInstance().world);
|
||||
if (worldRenderer != null && (ON_SAVE_RELOAD_ALL||ON_SAVE_RELOAD_RENDERER)) {
|
||||
//Shudown renderer
|
||||
((IGetVoxyRenderSystem) worldRenderer).shutdownRenderer();
|
||||
@@ -49,11 +48,11 @@ public class VoxyConfigScreenFactory implements ModMenuApi {
|
||||
if (world != null && ON_SAVE_RELOAD_ALL) {
|
||||
//This is a hack inserted for the client world thing
|
||||
//TODO: FIXME: MAKE BETTER
|
||||
var engine = ((IVoxyWorldGetter) world).getWorldEngine();
|
||||
var engine = world.getWorldEngine();
|
||||
if (engine != null) {
|
||||
VoxyCommon.getInstance().stopWorld(engine);
|
||||
}
|
||||
((IVoxyWorldSetter) world).setWorldEngine(null);
|
||||
world.setWorldEngine(null);
|
||||
}
|
||||
//Shutdown instance
|
||||
if (ON_SAVE_RELOAD_ALL) {
|
||||
|
||||
@@ -421,7 +421,11 @@ public class RenderDataFactory45 {
|
||||
|
||||
//TODO: swap this out for something not getting the next entry
|
||||
long A = this.sectionData[idx * 2];
|
||||
|
||||
long B = this.sectionData[idx * 2+1];
|
||||
if (ModelQueries.isFluid(B)) {
|
||||
this.blockMesher.putNext(0);
|
||||
continue;
|
||||
}
|
||||
//Example thing thats just wrong but as example
|
||||
this.blockMesher.putNext((long) (false ? 0L : 1L) |
|
||||
((A & 0xFFFFL) << 26) |
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.cortex.voxy.client.mixin.minecraft;
|
||||
|
||||
import me.cortex.voxy.client.LoadException;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.network.*;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(ClientLoginNetworkHandler.class)
|
||||
public class MixinClientLoginNetworkHandler {
|
||||
@Inject(method = "<init>", at = @At(value = "TAIL"))
|
||||
private void voxy$init(ClientConnection connection, MinecraftClient client, ServerInfo serverInfo, Screen parentScreen, boolean newWorld, Duration worldLoadTime, Consumer statusConsumer, CookieStorage cookieStorage, CallbackInfo ci) {
|
||||
if (VoxyConfig.CONFIG.enabled) {
|
||||
VoxyCommon.createInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package me.cortex.voxy.client.mixin.minecraft;
|
||||
|
||||
import me.cortex.voxy.client.LoadException;
|
||||
import me.cortex.voxy.client.config.VoxyConfig;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import net.minecraft.client.network.ClientCommonNetworkHandler;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
public class MixinClientPlayNetworkHandler {
|
||||
@Inject(method = "onGameJoin", at = @At(value = "NEW", target = "(Lnet/minecraft/client/network/ClientPlayNetworkHandler;Lnet/minecraft/client/world/ClientWorld$Properties;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/registry/entry/RegistryEntry;IILnet/minecraft/client/render/WorldRenderer;ZJI)Lnet/minecraft/client/world/ClientWorld;", shift = At.Shift.BEFORE))
|
||||
private void voxy$init(GameJoinS2CPacket packet, CallbackInfo ci) {
|
||||
if (VoxyConfig.CONFIG.enabled) {
|
||||
VoxyCommon.createInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,8 @@ import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||
import me.cortex.voxy.client.core.rendering.VoxyRenderSystem;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldGetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldSetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||
import net.minecraft.client.render.*;
|
||||
import net.minecraft.client.util.ObjectAllocator;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
@@ -54,11 +52,11 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
|
||||
this.shutdownRenderer();
|
||||
|
||||
if (this.world != null) {
|
||||
var engine = ((IVoxyWorldGetter)this.world).getWorldEngine();
|
||||
var engine = ((IVoxyWorld)this.world).getWorldEngine();
|
||||
if (engine != null) {
|
||||
VoxyCommon.getInstance().stopWorld(engine);
|
||||
}
|
||||
((IVoxyWorldSetter)this.world).setWorldEngine(null);
|
||||
((IVoxyWorld)this.world).setWorldEngine(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,11 @@ public class WorldEngine {
|
||||
|
||||
|
||||
public void free() {
|
||||
//Cannot free while there are loaded sections
|
||||
if (this.sectionTracker.getLoadedCacheCount() != 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
this.thisTracker.free();
|
||||
this.isLive = false;
|
||||
try {this.mapper.close();} catch (Exception e) {Logger.error(e);}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.cortex.voxy.common.world.service;
|
||||
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.voxelization.ILightingSupplier;
|
||||
import me.cortex.voxy.common.voxelization.VoxelizedSection;
|
||||
@@ -8,15 +7,13 @@ import me.cortex.voxy.common.voxelization.WorldConversionFactory;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.common.thread.ServiceSlice;
|
||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldGetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||
import net.minecraft.util.math.ChunkSectionPos;
|
||||
import net.minecraft.world.LightType;
|
||||
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
public class VoxelIngestService {
|
||||
@@ -80,7 +77,7 @@ public class VoxelIngestService {
|
||||
}
|
||||
|
||||
public void enqueueIngest(WorldChunk chunk, boolean ignoreOnNullWorld) {
|
||||
var engine = ((IVoxyWorldGetter)chunk.getWorld()).getWorldEngine();
|
||||
var engine = ((IVoxyWorld)chunk.getWorld()).getWorldEngine();
|
||||
if (engine == null) {
|
||||
if (!ignoreOnNullWorld) {
|
||||
Logger.error("Could not ingest chunk as does not have world engine");
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.cortex.voxy.commonImpl;
|
||||
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
|
||||
public interface IVoxyWorldSetter {
|
||||
public interface IVoxyWorld {
|
||||
WorldEngine getWorldEngine();
|
||||
void setWorldEngine(WorldEngine engine);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package me.cortex.voxy.commonImpl;
|
||||
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
|
||||
public interface IVoxyWorldGetter {
|
||||
WorldEngine getWorldEngine();
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class VoxyCommon implements ModInitializer {
|
||||
}
|
||||
|
||||
//This is hardcoded like this because people do not understand what they are doing
|
||||
private static final boolean GlobalVerificationDisableOverride = false;//System.getProperty("voxy.verificationDisableOverride", "false").equals("true");
|
||||
private static final boolean GlobalVerificationDisableOverride = true;//System.getProperty("voxy.verificationDisableOverride", "false").equals("true");
|
||||
public static boolean isVerificationFlagOn(String name) {
|
||||
return (!GlobalVerificationDisableOverride) && System.getProperty("voxy."+name, "true").equals("true");
|
||||
}
|
||||
|
||||
@@ -128,6 +128,18 @@ public class VoxyInstance {
|
||||
|
||||
this.importManager.cancelImport(world);
|
||||
|
||||
if (world.getActiveSectionCount() != 0) {
|
||||
Logger.warn("Waiting for world to finish use");
|
||||
while (world.getActiveSectionCount() != 0) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: maybe replace the flush with an atomic "in queue" counter that is per world
|
||||
this.flush();
|
||||
|
||||
world.free();
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
package me.cortex.voxy.commonImpl.mixin.minecraft;
|
||||
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldGetter;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorldSetter;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.block.NeighborUpdater;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(World.class)
|
||||
public class MixinWorld implements IVoxyWorldGetter, IVoxyWorldSetter {
|
||||
@Shadow @Final protected NeighborUpdater neighborUpdater;
|
||||
public class MixinWorld implements IVoxyWorld {
|
||||
@Unique private WorldEngine voxyWorld;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -54,8 +54,8 @@ void setupScreenspace(in UnpackedNode node) {
|
||||
//NOTE!: cant this be precomputed and put in an array?? in the scene uniform??
|
||||
vec4 pPoint = (VP*vec4(vec3((i&1)!=0,(i&2)!=0,(i&4)!=0)*(32<<node.lodLevel),1));//Size of section is 32x32x32 (need to change it to a bounding box in the future)
|
||||
pPoint += base;
|
||||
zThing = max(pPoint.z, zThing);
|
||||
vec3 point = pPoint.xyz/pPoint.w;
|
||||
zThing = max(point.z, zThing);
|
||||
//TODO: CLIP TO VIEWPORT
|
||||
minBB = min(minBB, point);
|
||||
maxBB = max(maxBB, point);
|
||||
@@ -80,7 +80,7 @@ void setupScreenspace(in UnpackedNode node) {
|
||||
|
||||
//Checks if the node is implicitly culled (outside frustum)
|
||||
bool outsideFrustum() {
|
||||
return any(lessThanEqual(maxBB, vec3(0.0f))) || any(lessThanEqual(vec3(1.0f), minBB)) || zThing < 0;
|
||||
return any(lessThanEqual(maxBB, vec3(0.0f))) || any(lessThanEqual(vec3(1.0f), minBB)) || zThing < 0;//
|
||||
|
||||
//|| any(lessThanEqual(minBB, vec3(0.0f, 0.0f, 0.0f))) || any(lessThanEqual(vec3(1.0f, 1.0f, 1.0f), maxBB));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"client": [
|
||||
"minecraft.MixinClientCommonNetworkHandler",
|
||||
"minecraft.MixinClientPlayNetworkHandler",
|
||||
"minecraft.MixinClientLoginNetworkHandler",
|
||||
"minecraft.MixinDebugHud",
|
||||
"minecraft.MixinMinecraftClient",
|
||||
"minecraft.MixinThreadExecutor",
|
||||
|
||||
Reference in New Issue
Block a user