mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
update optimize random tick
This commit is contained in:
@@ -5,19 +5,26 @@ Subject: [PATCH] optimize random tick
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
|
||||
index 46e171ca454253c32e22c0c18587e9a7ba19f331..6f4d6e588b24490f3f543ed90cfe5a0b8eb15386 100644
|
||||
index 46e171ca454253c32e22c0c18587e9a7ba19f331..b0f4ef60832d2e301a9a31716d888638d9250c14 100644
|
||||
--- a/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -635,6 +635,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
||||
@@ -634,7 +634,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
||||
list.clear();
|
||||
}
|
||||
|
||||
this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations
|
||||
+ this.level.randomTickSystem.tick(this.level); // Leaf - optimize random tick
|
||||
- this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations
|
||||
+ // Leaf start - optimize random tick
|
||||
+ if (org.dreeam.leaf.config.modules.opt.OptimizeRandomTick.enabled) {
|
||||
+ this.level.randomTickSystem.tick(this.level);
|
||||
+ } else {
|
||||
+ this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations
|
||||
+ }
|
||||
+ // Leaf end - optimize random tick
|
||||
if (_boolean) {
|
||||
this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
|
||||
}
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index 27da552e2542153a58d6177f592cf30d858c41a9..a180612fea46ec9f7dcfe1781e985dd2e98ed513 100644
|
||||
index 27da552e2542153a58d6177f592cf30d858c41a9..35fb0770eb385e3837cb29711905c41b899bac8f 100644
|
||||
--- a/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -1114,6 +1114,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
@@ -28,28 +35,19 @@ index 27da552e2542153a58d6177f592cf30d858c41a9..a180612fea46ec9f7dcfe1781e985dd2
|
||||
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
|
||||
final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting
|
||||
ChunkPos pos = chunk.getPos();
|
||||
@@ -1129,7 +1130,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
} // Paper - Option to disable ice and snow
|
||||
|
||||
if (randomTickSpeed > 0) {
|
||||
- this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking
|
||||
+ if (org.dreeam.leaf.config.modules.opt.OptimizeRandomTick.enabled) randomTickSystem.tickChunk(this.simpleRandom, chunk, randomTickSpeed); // Leaf - optimize random tick
|
||||
+ else this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking // Leaf - optimize random tick
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab627b0128 100644
|
||||
index a3674ddb883eecb255279375a5e2eece7e016c0f..193009af4e477660ce36edda2658f09983941592 100644
|
||||
--- a/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
@@ -152,6 +152,48 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
|
||||
@@ -152,6 +152,61 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
|
||||
}
|
||||
// Gale end - Airplane - optimize random calls in chunk ticking - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively
|
||||
|
||||
+ // Leaf start - optimize random tick
|
||||
+ private boolean leaf$tickingBlocksDirty = true;
|
||||
+ private int leaf$tickingBlocksCount;
|
||||
+ private int leaf$tickingBlocksCount = 0;
|
||||
+ private int leaf$firstTickingSectionIndex = -1;
|
||||
+ private int leaf$mostTickingSectionIndex = -1;
|
||||
+ public final int leaf$tickingBlocksCount() {
|
||||
+ if (!leaf$tickingBlocksDirty) {
|
||||
+ return leaf$tickingBlocksCount;
|
||||
@@ -57,12 +55,17 @@ index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab
|
||||
+ leaf$tickingBlocksDirty = false;
|
||||
+ int sum = 0;
|
||||
+ leaf$firstTickingSectionIndex = -1;
|
||||
+ leaf$mostTickingSectionIndex = -1;
|
||||
+ int most = 0;
|
||||
+ for (int i = 0; i < sections.length; i++) {
|
||||
+ LevelChunkSection section = sections[i];
|
||||
+ int size = section.moonrise$getTickingBlockList().size();
|
||||
+ var list = sections[i].moonrise$getTickingBlockList();
|
||||
+ int size = list.size();
|
||||
+ if (size != 0 && leaf$firstTickingSectionIndex == -1) {
|
||||
+ leaf$firstTickingSectionIndex = i;
|
||||
+ }
|
||||
+ if (size > most) {
|
||||
+ leaf$mostTickingSectionIndex = i;
|
||||
+ }
|
||||
+ sum += size;
|
||||
+ }
|
||||
+ leaf$tickingBlocksCount = sum;
|
||||
@@ -70,16 +73,23 @@ index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab
|
||||
+ }
|
||||
+ public final java.util.OptionalLong leaf$getTickingPos(int idx) {
|
||||
+ if (leaf$firstTickingSectionIndex != -1) {
|
||||
+ int most = leaf$mostTickingSectionIndex;
|
||||
+ var mostList = sections[most].moonrise$getTickingBlockList();
|
||||
+ int mostSectionSize = mostList.size();
|
||||
+ if (idx < mostSectionSize) {
|
||||
+ short loc = mostList.getRaw(idx);
|
||||
+ return java.util.OptionalLong.of(BlockPos.asLong((loc & 15) | (locX << 4), (loc >>> 8) | ((getMinSectionY() + most) << 4), ((loc >>> 4) & 15) | (locZ << 4)));
|
||||
+ }
|
||||
+ idx -= mostSectionSize;
|
||||
+ for (int i = leaf$firstTickingSectionIndex; i < sections.length; i++) {
|
||||
+ LevelChunkSection section = sections[i];
|
||||
+ var l = section.moonrise$getTickingBlockList();
|
||||
+ int size = l.size();
|
||||
+ if (i == most) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ var list = sections[i].moonrise$getTickingBlockList();
|
||||
+ int size = list.size();
|
||||
+ if (idx < size) {
|
||||
+ short loc = l.getRaw(idx);
|
||||
+ int x = (loc & 15) | (chunkPos.x << 4);
|
||||
+ int y = (loc >>> 8) | ((getMinSectionY() + i) << 4);
|
||||
+ int z = ((loc >>> 4) & 15) | (chunkPos.z << 4);
|
||||
+ return java.util.OptionalLong.of(BlockPos.asLong(x, y, z));
|
||||
+ short loc = list.getRaw(idx);
|
||||
+ return java.util.OptionalLong.of(BlockPos.asLong((loc & 15) | (locX << 4), (loc >>> 8) | ((getMinSectionY() + i) << 4), ((loc >>> 4) & 15) | (locZ << 4)));
|
||||
+ }
|
||||
+ idx -= size;
|
||||
+ }
|
||||
@@ -91,7 +101,7 @@ index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab
|
||||
public LevelChunk(Level level, ChunkPos pos) {
|
||||
this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
|
||||
}
|
||||
@@ -416,6 +458,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
|
||||
@@ -416,6 +471,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
|
||||
if (blockState == state) {
|
||||
return null;
|
||||
} else {
|
||||
|
||||
@@ -100,7 +100,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194
|
||||
- // Paper end - detailed watchdog information
|
||||
}
|
||||
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
|
||||
index 36e592ff42eba050829f9c4d055c3d49a78ba813..b32cb1c85a9f7a7a96a257c4546ee7e21cd91a6d 100644
|
||||
index b0f4ef60832d2e301a9a31716d888638d9250c14..bfce7501918a120f0fa84699ab6f70bacfe92ec9 100644
|
||||
--- a/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -623,8 +623,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
||||
@@ -117,10 +117,10 @@ index 36e592ff42eba050829f9c4d055c3d49a78ba813..b32cb1c85a9f7a7a96a257c4546ee7e2
|
||||
|
||||
for (LevelChunk levelChunk : list) {
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index a180612fea46ec9f7dcfe1781e985dd2e98ed513..7cb8329fc787aa6a9f877afb43dc9cd655cb0e90 100644
|
||||
index 35fb0770eb385e3837cb29711905c41b899bac8f..fd6fe51ccac5163e70569484239bebeb79348501 100644
|
||||
--- a/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -1518,13 +1518,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
@@ -1517,13 +1517,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
// Paper end - log detailed entity tick information
|
||||
|
||||
public void tickNonPassenger(Entity entity) {
|
||||
@@ -134,7 +134,7 @@ index a180612fea46ec9f7dcfe1781e985dd2e98ed513..7cb8329fc787aa6a9f877afb43dc9cd6
|
||||
entity.setOldPosAndRot();
|
||||
entity.tickCount++;
|
||||
entity.totalEntityAge++; // Paper - age-like counter for all entities
|
||||
@@ -1537,13 +1531,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
@@ -1536,13 +1530,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||
for (Entity entity1 : entity.getPassengers()) {
|
||||
this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
|
||||
}
|
||||
@@ -149,7 +149,7 @@ index a180612fea46ec9f7dcfe1781e985dd2e98ed513..7cb8329fc787aa6a9f877afb43dc9cd6
|
||||
|
||||
private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
|
||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||
index fc39af434b59054260d763b994fb50bcf92b4ff3..ddc2a7fbca414b8e3c044cfe7076d2cac182b63e 100644
|
||||
index 4dce68aafbccfeda82d0bee127a59f43502c6f70..145c5f7a69710fe636bb5bca97f5a43fb778203d 100644
|
||||
--- a/net/minecraft/world/entity/Entity.java
|
||||
+++ b/net/minecraft/world/entity/Entity.java
|
||||
@@ -1166,16 +1166,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||
|
||||
@@ -1,39 +1,70 @@
|
||||
package org.dreeam.leaf.world;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.levelgen.BitRandomSource;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
|
||||
public final class RandomTickSystem {
|
||||
private static final long SCALE = 0x100000L;
|
||||
private static final long CHUNK_BLOCKS = 4096L;
|
||||
|
||||
/// reduce unnecessary sampling and block counting
|
||||
private static final long TICK_MASK = 0b11L;
|
||||
private static final long TICK_MUL = 4L;
|
||||
private static final long TICK_FILTER_MASK = 0b11L;
|
||||
private static final long CHUNK_BLOCKS = 4096L / 4L;
|
||||
private static final int BITS_STEP = 2;
|
||||
private static final int BITS_MAX = 60;
|
||||
|
||||
private final LongArrayList queue = new LongArrayList();
|
||||
private final LongArrayList samples = new LongArrayList();
|
||||
private final LongArrayList weights = new LongArrayList();
|
||||
private long weightsSum = 0L;
|
||||
|
||||
private int bits = 60;
|
||||
private long cacheRandom = 0L;
|
||||
|
||||
public void tick(ServerLevel world) {
|
||||
if (weights.isEmpty() || samples.isEmpty()) {
|
||||
final var random = world.simpleRandom;
|
||||
|
||||
final ReferenceList<LevelChunk> entityTickingChunks = world.moonrise$getEntityTickingChunks();
|
||||
final int randomTickSpeed = world.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
|
||||
final LevelChunk[] raw = entityTickingChunks.getRawDataUnchecked();
|
||||
final int size = entityTickingChunks.size();
|
||||
final boolean disableIceAndSnow = world.paperConfig().environment.disableIceAndSnow;
|
||||
if (randomTickSpeed <= 0) {
|
||||
return;
|
||||
}
|
||||
if (!disableIceAndSnow) {
|
||||
iceSnow(world, size, randomTickSpeed, random, raw);
|
||||
}
|
||||
final long weightsSum = fillWeight(size, random, raw, randomTickSpeed);
|
||||
if (weights.isEmpty() || samples.isEmpty() || weightsSum == 0L) {
|
||||
return;
|
||||
}
|
||||
sus(random, weightsSum);
|
||||
weights.clear();
|
||||
samples.clear();
|
||||
|
||||
final var random = world.simpleRandom;
|
||||
final var fullChunks = world.chunkSource.fullChunks;
|
||||
final long[] q = queue.elements();
|
||||
final int l = queue.size();
|
||||
LevelChunk a = null;
|
||||
long b = 0L;
|
||||
for (int k = 0; k < l; k++) {
|
||||
final long pos = q[k];
|
||||
if (a == null || b != pos) {
|
||||
a = fullChunks.get(pos);
|
||||
b = pos;
|
||||
}
|
||||
if (a != null) {
|
||||
tickBlock(world, a, random);
|
||||
}
|
||||
}
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
private void sus(BitRandomSource random, long weightsSum) {
|
||||
final long chosen;
|
||||
if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) {
|
||||
chosen = weightsSum / SCALE + 1L;
|
||||
@@ -44,62 +75,67 @@ public final class RandomTickSystem {
|
||||
return;
|
||||
}
|
||||
|
||||
final long spoke = weightsSum / chosen;
|
||||
if (spoke == 0L) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long[] weightsRaw = weights.elements();
|
||||
final long[] samplesRaw = samples.elements();
|
||||
|
||||
long accumulated = weightsRaw[0];
|
||||
final long spoke = weightsSum / chosen;
|
||||
if (spoke == 0L) return;
|
||||
long current = boundedNextLong(random, spoke);
|
||||
int i = 0;
|
||||
while (current < weightsSum) {
|
||||
while (accumulated < current) {
|
||||
i += 1;
|
||||
i++;
|
||||
accumulated += weightsRaw[i];
|
||||
}
|
||||
queue.add(samplesRaw[i]);
|
||||
current += spoke;
|
||||
}
|
||||
while (queue.size() < chosen) {
|
||||
queue.add(samplesRaw[i]);
|
||||
}
|
||||
|
||||
long[] queueRaw = queue.elements();
|
||||
int j = 0;
|
||||
int k;
|
||||
for (k = queue.size() - 3; j < k; j += 4) {
|
||||
final long packed1 = queueRaw[j];
|
||||
final long packed2 = queueRaw[j + 1];
|
||||
final long packed3 = queueRaw[j + 2];
|
||||
final long packed4 = queueRaw[j + 3];
|
||||
final LevelChunk chunk1 = getChunk(world, packed1);
|
||||
final LevelChunk chunk2 = packed1 != packed2 ? getChunk(world, packed2) : chunk1;
|
||||
final LevelChunk chunk3 = packed2 != packed3 ? getChunk(world, packed3) : chunk2;
|
||||
final LevelChunk chunk4 = packed3 != packed4 ? getChunk(world, packed4) : chunk3;
|
||||
if (chunk1 != null) tickBlock(world, chunk1, random);
|
||||
if (chunk2 != null) tickBlock(world, chunk2, random);
|
||||
if (chunk3 != null) tickBlock(world, chunk3, random);
|
||||
if (chunk4 != null) tickBlock(world, chunk4, random);
|
||||
private long fillWeight(int size, BitRandomSource random, LevelChunk[] raw, long randomTickSpeed) {
|
||||
int bits = 0;
|
||||
long cacheRandom = random.nextLong();
|
||||
long weightsSum = 0L;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (bits != BITS_MAX) {
|
||||
bits += BITS_STEP;
|
||||
} else {
|
||||
bits = 0;
|
||||
cacheRandom = random.nextLong();
|
||||
}
|
||||
for (k = queue.size(); j < k; j++) {
|
||||
final LevelChunk chunk = getChunk(world, queueRaw[j]);
|
||||
if (chunk != null) tickBlock(world, chunk, random);
|
||||
if ((cacheRandom & (TICK_FILTER_MASK << bits)) != 0L) {
|
||||
continue;
|
||||
}
|
||||
final var chunk = raw[i];
|
||||
final long count = chunk.leaf$tickingBlocksCount();
|
||||
if (count != 0L) {
|
||||
long weight = (randomTickSpeed * count * SCALE) / CHUNK_BLOCKS;
|
||||
samples.add(chunk.locX & 4294967295L | (chunk.locZ & 4294967295L) << 32);
|
||||
weights.add(weight);
|
||||
weightsSum += weight;
|
||||
}
|
||||
}
|
||||
return weightsSum;
|
||||
}
|
||||
|
||||
weightsSum = 0L;
|
||||
queue.clear();
|
||||
weights.clear();
|
||||
samples.clear();
|
||||
private static void iceSnow(ServerLevel world, int size, int randomTickSpeed, BitRandomSource random, LevelChunk[] raw) {
|
||||
int currentIceAndSnowTick = random.nextInt(48 * 16);
|
||||
for (int i = 0; i < size; i++) {
|
||||
currentIceAndSnowTick -= randomTickSpeed;
|
||||
if (currentIceAndSnowTick <= 0) {
|
||||
currentIceAndSnowTick = random.nextInt(48 * 16);
|
||||
LevelChunk chunk = raw[i];
|
||||
ChunkPos pos = chunk.getPos();
|
||||
int minBlockX = pos.getMinBlockX();
|
||||
int minBlockZ = pos.getMinBlockZ();
|
||||
world.tickPrecipitation(world.getBlockRandomPos(minBlockX, 0, minBlockZ, 15));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static LevelChunk getChunk(ServerLevel world, long packed) {
|
||||
return world.chunkSource.getChunkAtIfLoadedImmediately((int) packed, (int) (packed >> 32));
|
||||
}
|
||||
|
||||
private static void tickBlock(ServerLevel world, LevelChunk chunk, RandomSource random) {
|
||||
private static void tickBlock(ServerLevel world, LevelChunk chunk, BitRandomSource random) {
|
||||
int count = chunk.leaf$tickingBlocksCount();
|
||||
if (count == 0) {
|
||||
return;
|
||||
@@ -121,43 +157,13 @@ public final class RandomTickSystem {
|
||||
}
|
||||
}
|
||||
|
||||
public void tickChunk(
|
||||
RandomSource random,
|
||||
LevelChunk chunk,
|
||||
long tickSpeed
|
||||
) {
|
||||
if (this.bits == BITS_MAX) {
|
||||
this.bits = 0;
|
||||
this.cacheRandom = random.nextLong();
|
||||
} else {
|
||||
this.bits += BITS_STEP;
|
||||
}
|
||||
if ((this.cacheRandom & (TICK_MASK << bits)) == 0L) {
|
||||
long count = chunk.leaf$tickingBlocksCount();
|
||||
if (count != 0L) {
|
||||
long weight = (TICK_MUL * tickSpeed * count * SCALE) / CHUNK_BLOCKS;
|
||||
samples.add(chunk.getPos().longKey);
|
||||
weights.add(weight);
|
||||
weightsSum += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rng a random number generator to be used as a
|
||||
* source of pseudorandom {@code long} values
|
||||
* @param bound the upper bound (exclusive); must be greater than zero
|
||||
*
|
||||
* @return a pseudorandomly chosen {@code long} value
|
||||
*
|
||||
* @see java.util.random.RandomGenerator#nextLong(long) nextLong(bound)
|
||||
*/
|
||||
public static long boundedNextLong(RandomSource rng, long bound) {
|
||||
final long m = bound - 1;
|
||||
private static long boundedNextLong(BitRandomSource rng, long bound) {
|
||||
final long m = bound - 1L;
|
||||
long r = rng.nextLong();
|
||||
if ((bound & m) == 0L) {
|
||||
r &= m;
|
||||
} else {
|
||||
//noinspection StatementWithEmptyBody
|
||||
for (long u = r >>> 1;
|
||||
u + m - (r = u % bound) < 0L;
|
||||
u = rng.nextLong() >>> 1)
|
||||
|
||||
Reference in New Issue
Block a user