mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
initial commit
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Taiyou06 <kaandindar21@gmail.com>
|
||||
Date: Sat, 12 Jul 2025 02:00:43 +0200
|
||||
Subject: [PATCH] Optimise-getEntities
|
||||
|
||||
Co-authored by: Martijn Muijsers <martijnmuijsers@live.nl>
|
||||
|
||||
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
|
||||
index 5ebc1b2cf186b512da5b62fedba16b612f2fa6ed..14dc37f0ed08b4eb4c7740ec68d23df0060c0955 100644
|
||||
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
|
||||
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
|
||||
@@ -579,6 +579,14 @@ public final class ChunkEntitySlices {
|
||||
|
||||
final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
||||
|
||||
+ // Cache AABB fields to local variables to reduce field accesses inside the hot loop.
|
||||
+ final double boxMinX = box.minX;
|
||||
+ final double boxMinY = box.minY;
|
||||
+ final double boxMinZ = box.minZ;
|
||||
+ final double boxMaxX = box.maxX;
|
||||
+ final double boxMaxY = box.maxY;
|
||||
+ final double boxMaxZ = box.maxZ;
|
||||
+
|
||||
for (int section = min; section <= max; ++section) {
|
||||
final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
||||
|
||||
@@ -587,11 +595,18 @@ public final class ChunkEntitySlices {
|
||||
}
|
||||
|
||||
final Entity[] storage = list.storage;
|
||||
+ final int len = Math.min(storage.length, list.size());
|
||||
|
||||
- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
||||
+ for (int i = 0; i < len; ++i) {
|
||||
final Entity entity = storage[i];
|
||||
|
||||
- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
|
||||
+ if (entity == null || entity == except) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // Inline AABB#intersects to avoid a method call and use the cached AABB fields.
|
||||
+ final AABB entityBB = entity.getBoundingBox();
|
||||
+ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -618,19 +633,32 @@ public final class ChunkEntitySlices {
|
||||
|
||||
final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
||||
|
||||
+ // Cache AABB fields to local variables to reduce field accesses inside the hot loop.
|
||||
+ final double boxMinX = box.minX;
|
||||
+ final double boxMinY = box.minY;
|
||||
+ final double boxMinZ = box.minZ;
|
||||
+ final double boxMaxX = box.maxX;
|
||||
+ final double boxMaxY = box.maxY;
|
||||
+ final double boxMaxZ = box.maxZ;
|
||||
+
|
||||
for (int section = min; section <= max; ++section) {
|
||||
final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
||||
-
|
||||
if (list == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Entity[] storage = list.storage;
|
||||
+ final int len = Math.min(storage.length, list.size());
|
||||
|
||||
- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
||||
+ for (int i = 0; i < len; ++i) {
|
||||
final Entity entity = storage[i];
|
||||
+ if (entity == null || entity == except) {
|
||||
+ continue;
|
||||
+ }
|
||||
|
||||
- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
|
||||
+ // Inline AABB#intersects to avoid a method call and use the cached AABB fields.
|
||||
+ final AABB entityBB = entity.getBoundingBox();
|
||||
+ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Taiyou06 <kaandindar21@gmail.com>
|
||||
Date: Sat, 12 Jul 2025 02:00:43 +0200
|
||||
Subject: [PATCH] Optimize-getEntities
|
||||
|
||||
Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html)
|
||||
|
||||
Co-authored by: Martijn Muijsers <martijnmuijsers@live.nl>
|
||||
|
||||
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
|
||||
index 5ebc1b2cf186b512da5b62fedba16b612f2fa6ed..14dc37f0ed08b4eb4c7740ec68d23df0060c0955 100644
|
||||
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
|
||||
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
|
||||
@@ -579,6 +579,14 @@ public final class ChunkEntitySlices {
|
||||
|
||||
final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
||||
|
||||
+ // Cache AABB fields to local variables to reduce field accesses inside the hot loop.
|
||||
+ final double boxMinX = box.minX;
|
||||
+ final double boxMinY = box.minY;
|
||||
+ final double boxMinZ = box.minZ;
|
||||
+ final double boxMaxX = box.maxX;
|
||||
+ final double boxMaxY = box.maxY;
|
||||
+ final double boxMaxZ = box.maxZ;
|
||||
+
|
||||
for (int section = min; section <= max; ++section) {
|
||||
final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
||||
|
||||
@@ -587,11 +595,18 @@ public final class ChunkEntitySlices {
|
||||
}
|
||||
|
||||
final Entity[] storage = list.storage;
|
||||
+ final int len = Math.min(storage.length, list.size());
|
||||
|
||||
- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
||||
+ for (int i = 0; i < len; ++i) {
|
||||
final Entity entity = storage[i];
|
||||
|
||||
- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
|
||||
+ if (entity == null || entity == except) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // Inline AABB#intersects to avoid a method call and use the cached AABB fields.
|
||||
+ final AABB entityBB = entity.getBoundingBox();
|
||||
+ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -618,19 +633,32 @@ public final class ChunkEntitySlices {
|
||||
|
||||
final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
||||
|
||||
+ // Cache AABB fields to local variables to reduce field accesses inside the hot loop.
|
||||
+ final double boxMinX = box.minX;
|
||||
+ final double boxMinY = box.minY;
|
||||
+ final double boxMinZ = box.minZ;
|
||||
+ final double boxMaxX = box.maxX;
|
||||
+ final double boxMaxY = box.maxY;
|
||||
+ final double boxMaxZ = box.maxZ;
|
||||
+
|
||||
for (int section = min; section <= max; ++section) {
|
||||
final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
||||
-
|
||||
if (list == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Entity[] storage = list.storage;
|
||||
+ final int len = Math.min(storage.length, list.size());
|
||||
|
||||
- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
||||
+ for (int i = 0; i < len; ++i) {
|
||||
final Entity entity = storage[i];
|
||||
+ if (entity == null || entity == except) {
|
||||
+ continue;
|
||||
+ }
|
||||
|
||||
- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
|
||||
+ // Inline AABB#intersects to avoid a method call and use the cached AABB fields.
|
||||
+ final AABB entityBB = entity.getBoundingBox();
|
||||
+ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||
index d6be86981219f0fe0db15d36d9c85e9a56020373..a5fb2e4e264ab14b867a0b084ce7a461d3b5eaae 100644
|
||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||
@@ -282,13 +282,17 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
public List<DefaultDrop> drops = new java.util.ArrayList<>(); // Paper - Restore vanilla drops behavior
|
||||
public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
|
||||
public boolean collides = true;
|
||||
- public Set<UUID> collidableExemptions = new java.util.HashSet<>();
|
||||
+ public it.unimi.dsi.fastutil.ints.IntOpenHashSet collidableExemptions = new it.unimi.dsi.fastutil.ints.IntOpenHashSet(); // Leaf - optimize getEntities
|
||||
+ private boolean hasAnyExemptions = false; // Leaf - optimize getEntities
|
||||
+ private SynchedEntityData.DataItem<Float> healthDataItem; // Leaf - Cached reference to avoid array lookup
|
||||
public boolean bukkitPickUpLoot;
|
||||
public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
|
||||
public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
|
||||
public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
|
||||
public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; // Paper - Make shield blocking delay configurable
|
||||
protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur - API for any mob to burn daylight
|
||||
+ private Boolean cachedFixClimbingBypassingCrammingRule = null;
|
||||
+ private Level cachedConfigLevel = null;
|
||||
// CraftBukkit end
|
||||
|
||||
protected LivingEntity(EntityType<? extends LivingEntity> entityType, Level level) {
|
||||
@@ -304,6 +308,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
this.setYRot((float)(Math.random() * (float) (Math.PI * 2)));
|
||||
this.yHeadRot = this.getYRot();
|
||||
this.brain = this.makeBrain(EMPTY_BRAIN);
|
||||
+ healthDataItem = null;
|
||||
}
|
||||
|
||||
@Contract(
|
||||
@@ -1413,7 +1418,11 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
return (float) serverPlayer.getBukkitEntity().getHealth();
|
||||
}
|
||||
// CraftBukkit end
|
||||
- return this.entityData.get(DATA_HEALTH_ID);
|
||||
+
|
||||
+ // Optimize entity data access by caching the DataItem reference
|
||||
+ // This avoids the array lookup in entityData.get() for every call
|
||||
+ if (this.healthDataItem == null) {this.healthDataItem = this.entityData.getItem(DATA_HEALTH_ID);}
|
||||
+ return this.healthDataItem.getValue();
|
||||
}
|
||||
|
||||
public void setHealth(float health) {
|
||||
@@ -2282,7 +2291,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
- return !this.isRemoved() && this.getHealth() > 0.0F && !this.dead; // Paper - Check this.dead
|
||||
+ return !this.dead && !this.isRemoved() && this.getHealth() > 0.0F; // Paper - Check this.dead // Leaf - check the cheapest first
|
||||
}
|
||||
|
||||
public boolean isLookingAtMe(LivingEntity entity, double tolerance, boolean scaleByDistance, boolean visual, double... yValues) {
|
||||
@@ -4044,10 +4053,21 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
return !this.isRemoved() && this.collides; // CraftBukkit
|
||||
}
|
||||
|
||||
+ // Leaf start - optimize getEntities
|
||||
+ private boolean getFixClimbingBypassingCrammingRule() {
|
||||
+ Level currentLevel = this.level();
|
||||
+ if (this.cachedFixClimbingBypassingCrammingRule == null || this.cachedConfigLevel != currentLevel) {
|
||||
+ this.cachedConfigLevel = currentLevel;
|
||||
+ this.cachedFixClimbingBypassingCrammingRule = currentLevel.paperConfig().collisions.fixClimbingBypassingCrammingRule;
|
||||
+ }
|
||||
+ return this.cachedFixClimbingBypassingCrammingRule;
|
||||
+ }
|
||||
+ // Leaf end - optimize getEntities
|
||||
+
|
||||
// Paper start - Climbing should not bypass cramming gamerule
|
||||
@Override
|
||||
public boolean isPushable() {
|
||||
- return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule);
|
||||
+ return this.isCollidable(this.getFixClimbingBypassingCrammingRule()); // Leaf - optimize getEntities
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -4059,7 +4079,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||
// CraftBukkit start - collidable API
|
||||
@Override
|
||||
public boolean canCollideWithBukkit(Entity entity) {
|
||||
- return this.isPushable() && this.collides != this.collidableExemptions.contains(entity.getUUID());
|
||||
+ return this.isPushable() && this.collides && (!hasAnyExemptions || !collidableExemptions.contains(entity.getId()));
|
||||
}
|
||||
// CraftBukkit end
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Taiyou06 <kaandindar21@gmail.com>
|
||||
Date: Mon, 21 Jul 2025 19:16:53 +0200
|
||||
Subject: [PATCH] cache BlockBehaviour
|
||||
|
||||
Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html)
|
||||
|
||||
diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
|
||||
index 611887f5f8f218f5ec1ad19580f3123a60b20d46..3ea4157d7e57d7c7bc3af751593541f61fca7393 100644
|
||||
--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
|
||||
+++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
|
||||
@@ -982,19 +982,25 @@ public abstract class BlockBehaviour implements FeatureElement {
|
||||
}
|
||||
|
||||
public boolean is(TagKey<Block> tag) {
|
||||
- return this.getBlock().builtInRegistryHolder().is(tag);
|
||||
+ // Cache the holder reference to avoid multiple calls
|
||||
+ return this.owner.builtInRegistryHolder().is(tag);
|
||||
}
|
||||
|
||||
public boolean is(TagKey<Block> tag, Predicate<BlockBehaviour.BlockStateBase> predicate) {
|
||||
- return this.is(tag) && predicate.test(this);
|
||||
+ // Optimize by avoiding the redundant is() call and using cached holder
|
||||
+ Holder<Block> holder = this.owner.builtInRegistryHolder();
|
||||
+ return holder.is(tag) && predicate.test(this);
|
||||
}
|
||||
|
||||
public boolean is(HolderSet<Block> holder) {
|
||||
- return holder.contains(this.getBlock().builtInRegistryHolder());
|
||||
+ // Cache the block holder reference
|
||||
+ return holder.contains(this.owner.builtInRegistryHolder());
|
||||
}
|
||||
|
||||
public boolean is(Holder<Block> block) {
|
||||
- return this.is(block.value());
|
||||
+ // Direct reference comparison first (fastest), then fallback to value comparison
|
||||
+ Holder<Block> thisHolder = this.owner.builtInRegistryHolder();
|
||||
+ return thisHolder == block || this.owner == block.value();
|
||||
}
|
||||
|
||||
public Stream<TagKey<Block>> getTags() {
|
||||
@@ -1011,11 +1017,13 @@ public abstract class BlockBehaviour implements FeatureElement {
|
||||
}
|
||||
|
||||
public boolean is(Block block) {
|
||||
- return this.getBlock() == block;
|
||||
+ // we use owner directly
|
||||
+ return this.owner == block;
|
||||
}
|
||||
|
||||
public boolean is(ResourceKey<Block> block) {
|
||||
- return this.getBlock().builtInRegistryHolder().is(block);
|
||||
+ // Cache the holder reference
|
||||
+ return this.owner.builtInRegistryHolder().is(block);
|
||||
}
|
||||
|
||||
public final FluidState getFluidState() { // Paper - Perf: Final for inlining
|
||||
@@ -0,0 +1,20 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Taiyou06 <kaandindar21@gmail.com>
|
||||
Date: Mon, 21 Jul 2025 19:39:45 +0200
|
||||
Subject: [PATCH] entity to uuid set
|
||||
|
||||
Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html)
|
||||
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
|
||||
index fc4fa99a993a017676da2be3cb254399d421bce1..76a164df551dd5660fde09230d568ef7e8dcce54 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
|
||||
@@ -901,7 +901,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
|
||||
|
||||
@Override
|
||||
public Set<UUID> getCollidableExemptions() {
|
||||
- return this.getHandle().collidableExemptions;
|
||||
+ return new org.dreeam.leaf.util.map.EntityIdToUuidSet(this.getHandle().collidableExemptions, this.getHandle().level());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
*Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html)
|
||||
*/
|
||||
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityIdToUuidSet extends AbstractSet<UUID> {
|
||||
private final it.unimi.dsi.fastutil.ints.IntOpenHashSet backing;
|
||||
private final Level level;
|
||||
|
||||
public EntityIdToUuidSet(it.unimi.dsi.fastutil.ints.IntOpenHashSet backing, Level level) {
|
||||
this.backing = backing;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (!(o instanceof UUID uuid)) return false;
|
||||
Entity entity = ((ServerLevel) level).getEntity(uuid);
|
||||
return entity != null && backing.contains(entity.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(UUID uuid) {
|
||||
Entity entity = ((ServerLevel) level).getEntity(uuid);
|
||||
if (entity == null) return false;
|
||||
return backing.add(entity.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if (!(o instanceof UUID uuid)) return false;
|
||||
Entity entity = ((ServerLevel) level).getEntity(uuid);
|
||||
return entity != null && backing.remove(entity.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<UUID> iterator() {
|
||||
return new Iterator<UUID>() {
|
||||
private final it.unimi.dsi.fastutil.ints.IntIterator intIterator = backing.iterator();
|
||||
private UUID nextUuid = null;
|
||||
private boolean hasPrecomputed = false;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (!hasPrecomputed) {
|
||||
computeNext();
|
||||
}
|
||||
return nextUuid != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
UUID result = nextUuid;
|
||||
hasPrecomputed = false;
|
||||
nextUuid = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
intIterator.remove();
|
||||
}
|
||||
|
||||
private void computeNext() {
|
||||
while (intIterator.hasNext()) {
|
||||
int entityId = intIterator.nextInt();
|
||||
Entity entity = level.getEntity(entityId);
|
||||
if (entity != null) {
|
||||
nextUuid = entity.getUUID();
|
||||
hasPrecomputed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
nextUuid = null;
|
||||
hasPrecomputed = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return backing.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
backing.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user