Implement entity activation range
This commit is contained in:
parent
24db9e1cc0
commit
5605db762f
@ -0,0 +1,6 @@
|
|||||||
|
package io.izzel.arclight.common.bridge.entity;
|
||||||
|
|
||||||
|
public interface AgeableEntityBridge extends LivingEntityBridge {
|
||||||
|
|
||||||
|
boolean bridge$isAgeLocked();
|
||||||
|
}
|
||||||
@ -54,6 +54,4 @@ public interface EntityBridge extends ICommandSourceBridge {
|
|||||||
int bridge$getRideCooldown();
|
int bridge$getRideCooldown();
|
||||||
|
|
||||||
boolean bridge$canCollideWith(Entity entity);
|
boolean bridge$canCollideWith(Entity entity);
|
||||||
|
|
||||||
void bridge$inactiveTick();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package io.izzel.arclight.common.mixin.core.entity;
|
package io.izzel.arclight.common.mixin.core.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.common.bridge.entity.AgeableEntityBridge;
|
||||||
import net.minecraft.entity.AgeableEntity;
|
import net.minecraft.entity.AgeableEntity;
|
||||||
import net.minecraft.nbt.CompoundNBT;
|
import net.minecraft.nbt.CompoundNBT;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
@ -14,7 +15,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@Mixin(AgeableEntity.class)
|
@Mixin(AgeableEntity.class)
|
||||||
public abstract class AgeableEntityMixin extends CreatureEntityMixin {
|
public abstract class AgeableEntityMixin extends CreatureEntityMixin implements AgeableEntityBridge {
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
@Shadow public abstract boolean isChild();
|
@Shadow public abstract boolean isChild();
|
||||||
@ -38,4 +39,9 @@ public abstract class AgeableEntityMixin extends CreatureEntityMixin {
|
|||||||
private boolean arclight$tickIfNotLocked(World world) {
|
private boolean arclight$tickIfNotLocked(World world) {
|
||||||
return world.isRemote || ageLocked;
|
return world.isRemote || ageLocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean bridge$isAgeLocked() {
|
||||||
|
return this.ageLocked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,6 @@ import org.bukkit.event.vehicle.VehicleEnterEvent;
|
|||||||
import org.bukkit.event.vehicle.VehicleExitEvent;
|
import org.bukkit.event.vehicle.VehicleExitEvent;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.projectiles.ProjectileSource;
|
import org.bukkit.projectiles.ProjectileSource;
|
||||||
import org.spigotmc.ActivationRange;
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Overwrite;
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
@ -201,41 +200,18 @@ public abstract class EntityMixin implements InternalEntityBridge, EntityBridge,
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
private static final int CURRENT_LEVEL = 2;
|
private static final int CURRENT_LEVEL = 2;
|
||||||
public boolean persist;
|
public boolean persist = true;
|
||||||
public boolean valid;
|
public boolean valid;
|
||||||
public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only
|
public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only
|
||||||
public boolean forceExplosionKnockback; // SPIGOT-949
|
public boolean forceExplosionKnockback; // SPIGOT-949
|
||||||
public org.spigotmc.ActivationRange.ActivationType activationType;
|
|
||||||
public boolean defaultActivationState;
|
|
||||||
public long activatedTick = Integer.MIN_VALUE;
|
|
||||||
public boolean persistentInvisibility = false;
|
public boolean persistentInvisibility = false;
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("RETURN"))
|
|
||||||
private void arclight$init(EntityType<?> entityTypeIn, World worldIn, CallbackInfo ci) {
|
|
||||||
this.persist = true;
|
|
||||||
activationType = ActivationRange.initializeEntityActivationType((Entity) (Object) this);
|
|
||||||
if (worldIn != null) {
|
|
||||||
this.defaultActivationState = ActivationRange.initializeEntityActivationState((Entity) (Object) this, ((WorldBridge) worldIn).bridge$spigotConfig());
|
|
||||||
} else {
|
|
||||||
this.defaultActivationState = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CraftEntity bukkitEntity;
|
private CraftEntity bukkitEntity;
|
||||||
|
|
||||||
public CraftEntity getBukkitEntity() {
|
public CraftEntity getBukkitEntity() {
|
||||||
return internal$getBukkitEntity();
|
return internal$getBukkitEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void inactiveTick() {
|
|
||||||
this.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bridge$inactiveTick() {
|
|
||||||
this.inactiveTick();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandSender bridge$getBukkitSender(CommandSource wrapper) {
|
public CommandSender bridge$getBukkitSender(CommandSource wrapper) {
|
||||||
return internal$getBukkitEntity();
|
return internal$getBukkitEntity();
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
package io.izzel.arclight.impl.bridge;
|
||||||
|
|
||||||
|
public interface EntityBridge_ActivationRange {
|
||||||
|
|
||||||
|
void bridge$inactiveTick();
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange;
|
||||||
|
|
||||||
|
import io.izzel.arclight.common.bridge.world.WorldBridge;
|
||||||
|
import io.izzel.arclight.impl.bridge.EntityBridge_ActivationRange;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import org.spigotmc.ActivationRange;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Entity.class)
|
||||||
|
public abstract class EntityMixin_ActivationRange implements EntityBridge_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow public abstract void recalculateSize();
|
||||||
|
@Shadow public int ticksExisted;
|
||||||
|
@Shadow public abstract void remove();
|
||||||
|
@Shadow public World world;
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
public ActivationRange.ActivationType activationType;
|
||||||
|
public boolean defaultActivationState;
|
||||||
|
public long activatedTick = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void arclight$init(EntityType<?> entityTypeIn, World worldIn, CallbackInfo ci) {
|
||||||
|
activationType = ActivationRange.initializeEntityActivationType((Entity) (Object) this);
|
||||||
|
if (worldIn != null) {
|
||||||
|
this.defaultActivationState = ActivationRange.initializeEntityActivationState((Entity) (Object) this, ((WorldBridge) worldIn).bridge$spigotConfig());
|
||||||
|
} else {
|
||||||
|
this.defaultActivationState = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void inactiveTick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bridge$inactiveTick() {
|
||||||
|
this.inactiveTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange;
|
||||||
|
|
||||||
|
import io.izzel.arclight.impl.bridge.EntityBridge_ActivationRange;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.world.server.ServerWorld;
|
||||||
|
import org.spigotmc.ActivationRange;
|
||||||
|
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.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
@Mixin(ServerWorld.class)
|
||||||
|
public class ServerWorldMixin_ActivationRange {
|
||||||
|
|
||||||
|
@Inject(method = "tick", at = @At(value = "INVOKE", remap = false, target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;int2ObjectEntrySet()Lit/unimi/dsi/fastutil/objects/ObjectSet;"))
|
||||||
|
private void activationRange$activateEntity(BooleanSupplier hasTimeLeft, CallbackInfo ci) {
|
||||||
|
ActivationRange.activateEntities((ServerWorld) (Object) this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "updateEntity", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;forceSetPosition(DDD)V"))
|
||||||
|
private void activationRange$inactiveTick(Entity entityIn, CallbackInfo ci) {
|
||||||
|
if (!ActivationRange.checkIfActive(entityIn)) {
|
||||||
|
if (entityIn.addedToChunk) {
|
||||||
|
++entityIn.ticksExisted;
|
||||||
|
if (entityIn.canUpdate()) {
|
||||||
|
((EntityBridge_ActivationRange) entityIn).bridge$inactiveTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.impl.mixin.optimization.general.activationrange.EntityMixin_ActivationRange;
|
||||||
|
import net.minecraft.entity.projectile.AbstractArrowEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
@Mixin(AbstractArrowEntity.class)
|
||||||
|
public abstract class AbstractArrowEntityMixin_ActivationRange extends EntityMixin_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow public boolean inGround;
|
||||||
|
@Shadow protected int timeInGround;
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inactiveTick() {
|
||||||
|
super.inactiveTick();
|
||||||
|
if (this.inGround) {
|
||||||
|
this.timeInGround++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.common.bridge.entity.AgeableEntityBridge;
|
||||||
|
import io.izzel.arclight.impl.mixin.optimization.general.activationrange.EntityMixin_ActivationRange;
|
||||||
|
import net.minecraft.entity.AgeableEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
@Mixin(AgeableEntity.class)
|
||||||
|
public abstract class AgeableEntityMixin_ActivationRange extends EntityMixin_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow public abstract int getGrowingAge();
|
||||||
|
@Shadow public abstract void setGrowingAge(int age);
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inactiveTick() {
|
||||||
|
super.inactiveTick();
|
||||||
|
if (((AgeableEntityBridge) this).bridge$isAgeLocked()) {
|
||||||
|
this.recalculateSize();
|
||||||
|
} else {
|
||||||
|
int i = this.getGrowingAge();
|
||||||
|
if (i < 0) {
|
||||||
|
++i;
|
||||||
|
this.setGrowingAge(i);
|
||||||
|
} else if (i > 0) {
|
||||||
|
--i;
|
||||||
|
this.setGrowingAge(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.impl.mixin.optimization.general.activationrange.EntityMixin_ActivationRange;
|
||||||
|
import net.minecraft.entity.AreaEffectCloudEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
@Mixin(AreaEffectCloudEntity.class)
|
||||||
|
public abstract class AreaEffectCloudEntityMixin_ActivationRange extends EntityMixin_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow public int waitTime;
|
||||||
|
@Shadow private int duration;
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inactiveTick() {
|
||||||
|
super.inactiveTick();
|
||||||
|
if (this.ticksExisted >= this.waitTime + this.duration) {
|
||||||
|
this.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.impl.mixin.optimization.general.activationrange.EntityMixin_ActivationRange;
|
||||||
|
import net.minecraft.entity.projectile.FireworkRocketEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
@Mixin(FireworkRocketEntity.class)
|
||||||
|
public abstract class FireworkRocketEntityMixin_ActivationRange extends EntityMixin_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow private int fireworkAge;
|
||||||
|
@Shadow public int lifetime;
|
||||||
|
@Shadow protected abstract void func_213893_k();
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inactiveTick() {
|
||||||
|
super.inactiveTick();
|
||||||
|
++this.fireworkAge;
|
||||||
|
if (!this.world.isRemote && this.fireworkAge > this.lifetime) {
|
||||||
|
this.func_213893_k();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.common.bridge.world.WorldBridge;
|
||||||
|
import io.izzel.arclight.common.mod.ArclightConstants;
|
||||||
|
import io.izzel.arclight.impl.mixin.optimization.general.activationrange.EntityMixin_ActivationRange;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.entity.item.ItemEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraftforge.event.ForgeEventFactory;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ItemEntity.class)
|
||||||
|
public abstract class ItemEntityMixin_ActivationRange extends EntityMixin_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow public int pickupDelay;
|
||||||
|
@Shadow public int age;
|
||||||
|
@Shadow(remap = false) public int lifespan;
|
||||||
|
@Shadow public abstract ItemStack getItem();
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Inject(method = "<init>(Lnet/minecraft/entity/EntityType;Lnet/minecraft/world/World;)V", at = @At("RETURN"))
|
||||||
|
private void activationRange$init(EntityType<? extends ItemEntity> entityType, World world, CallbackInfo ci) {
|
||||||
|
this.lifespan = ((WorldBridge) this.world).bridge$spigotConfig().itemDespawnRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>(Lnet/minecraft/world/World;DDDLnet/minecraft/item/ItemStack;)V", at = @At("RETURN"))
|
||||||
|
private void activationRange$init(World worldIn, double x, double y, double z, ItemStack stack, CallbackInfo ci) {
|
||||||
|
if (this.lifespan == 6000) {
|
||||||
|
this.lifespan = ((WorldBridge) this.world).bridge$spigotConfig().itemDespawnRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastTick = ArclightConstants.currentTick - 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inactiveTick() {
|
||||||
|
super.inactiveTick();
|
||||||
|
int elapsedTicks = ArclightConstants.currentTick - this.lastTick;
|
||||||
|
if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks;
|
||||||
|
if (this.age != -32768) this.age += elapsedTicks;
|
||||||
|
this.lastTick = ArclightConstants.currentTick;
|
||||||
|
|
||||||
|
if (!this.world.isRemote && this.age >= this.lifespan) {
|
||||||
|
int hook = ForgeEventFactory.onItemExpire((ItemEntity) (Object) this, this.getItem());
|
||||||
|
if (hook < 0) this.remove();
|
||||||
|
else this.lifespan += hook;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.impl.mixin.optimization.general.activationrange.EntityMixin_ActivationRange;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
@Mixin(LivingEntity.class)
|
||||||
|
public abstract class LivingEntityMixin_ActivationRange extends EntityMixin_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow protected int idleTime;
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inactiveTick() {
|
||||||
|
super.inactiveTick();
|
||||||
|
this.idleTime++;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package io.izzel.arclight.impl.mixin.optimization.general.activationrange.entity;
|
||||||
|
|
||||||
|
import io.izzel.arclight.common.bridge.world.WorldBridge;
|
||||||
|
import io.izzel.arclight.impl.mixin.optimization.general.activationrange.EntityMixin_ActivationRange;
|
||||||
|
import net.minecraft.entity.merchant.villager.VillagerEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
@Mixin(VillagerEntity.class)
|
||||||
|
public abstract class VillagerEntityMixin_ActivationRange extends EntityMixin_ActivationRange {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow protected abstract void updateAITasks();
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inactiveTick() {
|
||||||
|
super.inactiveTick();
|
||||||
|
if (((WorldBridge) this.world).bridge$spigotConfig().tickInactiveVillagers
|
||||||
|
&& ((VillagerEntity) (Object) this).isServerWorld()) {
|
||||||
|
this.updateAITasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,15 @@
|
|||||||
"refmap": "mixins.arclight.impl.refmap.1_16.json",
|
"refmap": "mixins.arclight.impl.refmap.1_16.json",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"ClassInheritanceMultiMapMixin",
|
"ClassInheritanceMultiMapMixin",
|
||||||
"VoxelShapesMixin"
|
"VoxelShapesMixin",
|
||||||
|
"activationrange.EntityMixin_ActivationRange",
|
||||||
|
"activationrange.ServerWorldMixin_ActivationRange",
|
||||||
|
"activationrange.entity.AbstractArrowEntityMixin_ActivationRange",
|
||||||
|
"activationrange.entity.AgeableEntityMixin_ActivationRange",
|
||||||
|
"activationrange.entity.AreaEffectCloudEntityMixin_ActivationRange",
|
||||||
|
"activationrange.entity.FireworkRocketEntityMixin_ActivationRange",
|
||||||
|
"activationrange.entity.ItemEntityMixin_ActivationRange",
|
||||||
|
"activationrange.entity.LivingEntityMixin_ActivationRange",
|
||||||
|
"activationrange.entity.VillagerEntityMixin_ActivationRange"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user