9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-23 08:49:25 +00:00

Start on feature patches

This commit is contained in:
Samsuik
2025-01-16 21:54:41 +00:00
parent 39bba67ec9
commit 1e345a18b5
46 changed files with 2407 additions and 4 deletions

View File

@@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Tue, 21 Sep 2021 23:54:25 +0100
Subject: [PATCH] Client Visibility Settings API
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
index 1b5f3007a0afa7d007da84ba6afeb8b0185f6997..7dbfa59c02b0ce7d96796ff984d4a5879e9d6f95 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -66,6 +66,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
void setTrackingRangeModifier(double mod);
// Sakura end - entity tracking range modifier
+ // Sakura start - client visibility settings api
+ /**
+ * Server-side api to disable sending visual effects to the client.
+ *
+ * @return visibility api
+ */
+ me.samsuik.sakura.player.visibility.VisibilitySettings getVisibility();
+ // Sakura end - client visibility settings api
// Paper start
@Override

View File

@@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 9 Sep 2023 18:39:15 +0100
Subject: [PATCH] Merge Cannon Entities API
diff --git a/src/main/java/org/bukkit/entity/FallingBlock.java b/src/main/java/org/bukkit/entity/FallingBlock.java
index 0f7d64c1c0221ef7fa933df8c5572dbfdcba5128..78d62651760ff0c9ebc5c01d2417c8c86e116df5 100644
--- a/src/main/java/org/bukkit/entity/FallingBlock.java
+++ b/src/main/java/org/bukkit/entity/FallingBlock.java
@@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
/**
* Represents a falling block
*/
-public interface FallingBlock extends Entity {
+public interface FallingBlock extends Entity, me.samsuik.sakura.entity.merge.Mergeable { // Sakura - merge cannon entities api
// Sakura start - falling block height parity api
/**
diff --git a/src/main/java/org/bukkit/entity/TNTPrimed.java b/src/main/java/org/bukkit/entity/TNTPrimed.java
index 87e717c9ea61b0cbf536bc62fa829ddcfae5ad8c..2e89ea4e896bdea552ec40fca927920f5f96fd59 100644
--- a/src/main/java/org/bukkit/entity/TNTPrimed.java
+++ b/src/main/java/org/bukkit/entity/TNTPrimed.java
@@ -6,7 +6,7 @@ import org.jetbrains.annotations.Nullable;
/**
* Represents a Primed TNT.
*/
-public interface TNTPrimed extends Explosive {
+public interface TNTPrimed extends Explosive, me.samsuik.sakura.entity.merge.Mergeable { // Sakura - merge cannon entities api
/**
* Set the number of ticks until the TNT blows up after being primed.

View File

@@ -0,0 +1,48 @@
package me.samsuik.sakura.player.visibility;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface VisibilitySettings {
default boolean isEnabled(VisibilityType type) {
return this.get(type) == VisibilityState.ON;
}
default boolean isDisabled(VisibilityType type) {
return this.get(type) == VisibilityState.OFF;
}
default boolean isToggled(VisibilityType type) {
return !type.isDefault(this.get(type));
}
default VisibilityState toggle(VisibilityType type) {
VisibilityState state = this.get(type);
return this.set(type, toggleState(state));
}
default VisibilityState cycle(VisibilityType type) {
VisibilityState state = this.get(type);
return this.set(type, type.cycle(state));
}
default void toggleAll() {
VisibilityState state = this.currentState();
VisibilityState newState = toggleState(state);
for (VisibilityType type : VisibilityTypes.types()) {
this.set(type, newState);
}
}
VisibilityState get(VisibilityType type);
VisibilityState set(VisibilityType type, VisibilityState state);
VisibilityState currentState();
boolean playerModified();
static VisibilityState toggleState(VisibilityState state) {
return state != VisibilityState.OFF ? VisibilityState.OFF : VisibilityState.ON;
}
}

View File

@@ -0,0 +1,5 @@
package me.samsuik.sakura.player.visibility;
public enum VisibilityState {
ON, MODIFIED, MINIMAL, OFF;
}

View File

@@ -0,0 +1,35 @@
package me.samsuik.sakura.player.visibility;
import com.google.common.collect.ImmutableList;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record VisibilityType(String key, ImmutableList<VisibilityState> states) {
public VisibilityState getDefault() {
return this.states.getFirst();
}
public boolean isDefault(VisibilityState state) {
return state == this.getDefault();
}
public VisibilityState cycle(VisibilityState state) {
int index = this.states.indexOf(state);
int next = (index + 1) % this.states.size();
return this.states.get(next);
}
public static VisibilityType from(String key, boolean minimal) {
return new VisibilityType(key, states(minimal));
}
private static ImmutableList<VisibilityState> states(boolean minimal) {
ImmutableList.Builder<VisibilityState> listBuilder = ImmutableList.builder();
listBuilder.add(VisibilityState.ON);
if (minimal) {
listBuilder.add(VisibilityState.MINIMAL);
}
listBuilder.add(VisibilityState.OFF);
return listBuilder.build();
}
}

View File

@@ -0,0 +1,31 @@
package me.samsuik.sakura.player.visibility;
import com.google.common.collect.ImmutableList;
import org.jspecify.annotations.NullMarked;
import java.util.ArrayList;
import java.util.List;
@NullMarked
public final class VisibilityTypes {
private static final List<VisibilityType> TYPES = new ArrayList<>();
public static final VisibilityType TNT = register(create("tnt", true));
public static final VisibilityType SAND = register(create("sand", true));
public static final VisibilityType EXPLOSIONS = register(create("explosions", true));
public static final VisibilityType SPAWNERS = register(create("spawners", false));
public static final VisibilityType PISTONS = register(create("pistons", false));
public static ImmutableList<VisibilityType> types() {
return ImmutableList.copyOf(TYPES);
}
private static VisibilityType create(String key, boolean minimal) {
return VisibilityType.from(key, minimal);
}
private static VisibilityType register(VisibilityType type) {
TYPES.add(type);
return type;
}
}

View File

@@ -0,0 +1,251 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Tue, 21 Sep 2021 23:54:25 +0100
Subject: [PATCH] Client Visibility Settings
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 3a926d4d34a7c68a24b9e00bcbcff271c8992ad2..f1f322f623756f0c0a06a4207a12765c9623e151 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1749,6 +1749,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
profilerFiller.pop();
profilerFiller.pop();
serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions
+ serverLevel.explosionPositions.clear(); // Sakura - client visibility settings
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 8f9f759885a9cda57ae7d665ec309a57e12969fd..e096463443639e9eef5311d7154f6d2ac1517883 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -168,6 +168,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.handleLegacyStructureIndex(pos);
}
// Paper end - rewrite chunk system
+ private final it.unimi.dsi.fastutil.longs.Long2IntMap minimalEntities; // Sakura - client visibility settings; minimal tnt/sand
public ChunkMap(
ServerLevel level,
@@ -230,6 +231,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
);
this.setServerViewDistance(viewDistance);
this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, null, this::setChunkUnsaved); // Paper - rewrite chunk system
+ // Sakura start - client visibility settings; minimal tnt/sand
+ this.minimalEntities = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap();
+ this.minimalEntities.defaultReturnValue(Integer.MIN_VALUE);
+ // Sakura end - client visibility settings; minimal tnt/sand
}
private void setChunkUnsaved(ChunkPos chunkPos) {
@@ -954,6 +959,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
tracker.serverEntity.sendChanges();
}
}
+
+ this.minimalEntities.clear(); // Sakura - client visibility settings; minimal tnt/sand
}
// Paper end - optimise entity tracker
@@ -1164,6 +1171,32 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return !this.seenBy.isEmpty();
}
// Paper end - optimise entity tracker
+ // Sakura start - client visibility settings; entity visibility
+ private boolean checkEntityVisibility(final ServerPlayer player) {
+ final Entity entity = this.entity;
+ final me.samsuik.sakura.player.visibility.VisibilitySettings settings = player.visibilitySettings;
+ if (!settings.playerModified() || !(entity.isPrimedTNT || entity.isFallingBlock)) {
+ return true;
+ }
+ final me.samsuik.sakura.player.visibility.VisibilityType type;
+ if (entity.isPrimedTNT) {
+ type = me.samsuik.sakura.player.visibility.VisibilityTypes.TNT;
+ } else {
+ type = me.samsuik.sakura.player.visibility.VisibilityTypes.SAND;
+ }
+ final me.samsuik.sakura.player.visibility.VisibilityState state = settings.get(type);
+ if (state == me.samsuik.sakura.player.visibility.VisibilityState.MINIMAL) {
+ final long key = entity.blockPosition().asLong() ^ entity.getType().hashCode();
+ final long visibleEntity = ChunkMap.this.minimalEntities.get(key);
+ if (visibleEntity != Integer.MIN_VALUE) {
+ return entity.getId() == visibleEntity;
+ } else {
+ ChunkMap.this.minimalEntities.put(key, entity.getId());
+ }
+ }
+ return state != me.samsuik.sakura.player.visibility.VisibilityState.OFF;
+ }
+ // Sakura end - client visibility settings; entity visibility
public TrackedEntity(final Entity entity, final int range, final int updateInterval, final boolean trackDelta) {
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast, this.seenBy); // CraftBukkit
@@ -1234,6 +1267,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
// Paper end - Configurable entity tracking range by Y
+ flag = flag && this.checkEntityVisibility(player); // Sakura start - client visibility settings; entity visibility
// CraftBukkit start - respect vanish API
if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
flag = false;
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index b49a0c3eb4236c71e390f42278412dce59e98cbd..30454e69d603c3de3e2ecb5498d79c25bcc15a07 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -569,6 +569,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
);
}
// Paper end - chunk tick iteration
+ // Sakura start - client visibility settings
+ public final LongSet explosionPositions = new it.unimi.dsi.fastutil.longs.LongOpenHashSet();
+
+ public final boolean checkExplosionVisibility(final Vec3 position, final ServerPlayer player) {
+ final me.samsuik.sakura.player.visibility.VisibilitySettings settings = player.visibilitySettings;
+ if (settings.isDisabled(me.samsuik.sakura.player.visibility.VisibilityTypes.EXPLOSIONS)) {
+ return false;
+ } else if (settings.isToggled(me.samsuik.sakura.player.visibility.VisibilityTypes.EXPLOSIONS)) {
+ final BlockPos blockPosition = BlockPos.containing(position);
+ final long encodedPosition = blockPosition.asLong();
+ return this.explosionPositions.add(encodedPosition);
+ }
+ return true;
+ }
+ // Sakura end - client visibility settings
public ServerLevel(
MinecraftServer server,
@@ -1854,7 +1869,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
for (ServerPlayer serverPlayer : this.players) {
if (serverPlayer.distanceToSqr(vec3) < 4096.0) {
Optional<Vec3> optional = Optional.ofNullable(serverExplosion.getHitPlayers().get(serverPlayer));
- serverPlayer.connection.send(new ClientboundExplodePacket(vec3, optional, particleOptions, explosionSound));
+ // Sakura start - client visibility settings; let players toggle explosion particles
+ ParticleOptions particle = particleOptions;
+ Vec3 position = vec3;
+ // In 1.22 and later this should be replaced with sending the motion through a PlayerPositionPacket.
+ // The problem here is SetEntityMotion is capped to 3.9 b/pt and the only other alternate mean was
+ // implemented in 1.21.3. I believe it's best to just wait on this issue and deal with this hack.
+ if (!this.checkExplosionVisibility(vec3, serverPlayer)) {
+ position = new Vec3(0.0, -1024.0, 0.0);
+ particle = net.minecraft.core.particles.ParticleTypes.SMOKE;
+ }
+ serverPlayer.connection.send(new ClientboundExplodePacket(position, optional, particle, explosionSound));
+ // Sakura end - client visibility settings; let players toggle explosion particles
}
}
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 4535ea5fe788fc5c473d8289c031f2c9e56cfce6..92ee40a77f1b13c2c720cd41747206f82a735f9e 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -424,6 +424,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
}
// Paper end - rewrite chunk system
public double trackingRangeModifier = 1.0; // Sakura - entity tracking range modifier
+ public final me.samsuik.sakura.player.visibility.PlayerVisibilitySettings visibilitySettings = new me.samsuik.sakura.player.visibility.PlayerVisibilitySettings(); // Sakura - client visibility settings
public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) {
super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile);
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index e71c1a564e5d4ac43460f89879ff709ee685706f..7d2fe5df38db1d492ae65aa72959200221cf32d5 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -51,6 +51,21 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
protected static final net.minecraft.resources.ResourceLocation MINECRAFT_BRAND = net.minecraft.resources.ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
+ // Sakura start - client visibility settings
+ private @Nullable Packet<?> recreatePacket(final Packet<?> packet) {
+ final me.samsuik.sakura.player.visibility.VisibilitySettings settings = this.player.visibilitySettings;
+ if (packet instanceof net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket bedPacket) {
+ if (settings.isToggled(me.samsuik.sakura.player.visibility.VisibilityTypes.SPAWNERS) && bedPacket.getType() == net.minecraft.world.level.block.entity.BlockEntityType.MOB_SPAWNER) {
+ return null;
+ }
+ } else if (packet instanceof net.minecraft.network.protocol.game.ClientboundBlockEventPacket bePacket) {
+ if (settings.isToggled(me.samsuik.sakura.player.visibility.VisibilityTypes.PISTONS) && bePacket.getBlock() instanceof net.minecraft.world.level.block.piston.PistonBaseBlock) {
+ return null;
+ }
+ }
+ return packet;
+ }
+ // Sakura end - client visibility settings
public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit
this.server = server;
@@ -287,6 +302,12 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
} else if (packet instanceof net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket) {
this.player.compassTarget = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), this.getCraftPlayer().getWorld());
}
+ // Sakura start - client visibility settings
+ if (this.player.visibilitySettings.playerModified()) {
+ packet = this.recreatePacket(packet);
+ if (packet == null) return;
+ }
+ // Sakura end - client visibility settings
// CraftBukkit end
if (packet.isTerminal()) {
this.close();
@@ -299,7 +320,10 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
} catch (Throwable var7) {
CrashReport crashReport = CrashReport.forThrowable(var7, "Sending packet");
CrashReportCategory crashReportCategory = crashReport.addCategory("Packet being sent");
- crashReportCategory.setDetail("Packet class", () -> packet.getClass().getCanonicalName());
+ // Sakura start - client visibility settings; packet has to be effectively final
+ final Packet<?> packetFinal = packet;
+ crashReportCategory.setDetail("Packet class", () -> packetFinal.getClass().getCanonicalName());
+ // Sakura end - client visibility settings; packet has to be effectively final
throw new ReportedException(crashReport);
}
}
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 27ef385a85b13ceb58e8d149849983107c539b31..c44c9399ddbb34408b255550a98f5c54ecdf6aff 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3185,6 +3185,7 @@ public class ServerGamePacketListenerImpl
event.setCancelled(cancelled);
AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224
+ me.samsuik.sakura.player.gui.FeatureGui.clickEvent(event); // Sakura - client visibility settings
this.cserver.getPluginManager().callEvent(event);
if (this.player.containerMenu != oldContainer) {
return;
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 54a3ac38b70a9733173fb2ce4a7f86b534de1136..c4ace72f2eaf6a15c98490d18d3478b9ad5ed1a6 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -526,6 +526,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
// Paper end - optimise entity tracker
public boolean pushedByFluid = true; // Sakura - entity pushed by fluid api
+ // Sakura start - client visibility settings
+ public boolean isPrimedTNT;
+ public boolean isFallingBlock;
+ // Sakura end - client visibility settings
public Entity(EntityType<?> entityType, Level level) {
this.type = entityType;
diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java
index 6b708503367f989665b89a39d367935c80210d1d..5462a423be1317a306ca12ed10edcab636cd0c7f 100644
--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -73,6 +73,7 @@ public class FallingBlockEntity extends Entity {
public FallingBlockEntity(EntityType<? extends FallingBlockEntity> entityType, Level level) {
super(entityType, level);
this.heightParity = level.sakuraConfig().cannons.mechanics.fallingBlockParity; // Sakura - configure cannon mechanics
+ this.isFallingBlock = true; // Sakura - client visibility settings
}
public FallingBlockEntity(Level level, double x, double y, double z, BlockState state) {
diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java
index 3c673e5b8a52e322d41ed442ed06337cacb58771..8118911019f7fc81218a656e1ecbd7eada505741 100644
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -61,6 +61,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
public PrimedTnt(EntityType<? extends PrimedTnt> entityType, Level level) {
super(entityType, level);
this.blocksBuilding = true;
+ this.isPrimedTNT = true; // Sakura - client visibility settings
}
public PrimedTnt(Level level, double x, double y, double z, @Nullable LivingEntity owner) {

View File

@@ -0,0 +1,148 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 11 Sep 2021 19:19:41 +0100
Subject: [PATCH] Load Chunks on Movement
diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
index e04bd54744335fb5398c6e4f7ce8b981f35bfb7d..651a45b795818bd7b1364b95c52570fd99dd35e4 100644
--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
@@ -1885,6 +1885,7 @@ public final class CollisionUtil {
public static final int COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS = 1 << 1;
public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2;
public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3;
+ public static final int COLLISION_FLAG_ADD_TICKET = 1 << 4; // Sakura - load chunks on movement
public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb,
final List<VoxelShape> intoVoxel, final List<AABB> intoAABB,
@@ -1936,6 +1937,7 @@ public final class CollisionUtil {
final int maxChunkZ = maxBlockZ >> 4;
final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0;
+ final boolean addTicket = (collisionFlags & COLLISION_FLAG_ADD_TICKET) != 0; // Sakura - load chunks on movement
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
@@ -1954,6 +1956,13 @@ public final class CollisionUtil {
continue;
}
+ // Sakura start - load chunks on movement
+ if (addTicket && chunk.movementTicketNeedsUpdate() && chunkSource instanceof net.minecraft.server.level.ServerChunkCache chunkCache) {
+ final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(currChunkX, currChunkZ);
+ chunkCache.chunkMap.getDistanceManager().moonrise$getChunkHolderManager().addTicketAtLevel(net.minecraft.server.level.TicketType.ENTITY_MOVEMENT, currChunkX, currChunkZ, 31, chunkKey);
+ chunk.updatedMovementTicket();
+ }
+ // Sakura end - load chunks on movement
final LevelChunkSection[] sections = chunk.getSections();
// bound y
diff --git a/net/minecraft/server/level/TicketType.java b/net/minecraft/server/level/TicketType.java
index 8f12a4df5d63ecd11e6e615d910b6e3f6dde5f3c..56beffa0c5cdb0d6a4836a0ee496bd638432b143 100644
--- a/net/minecraft/server/level/TicketType.java
+++ b/net/minecraft/server/level/TicketType.java
@@ -21,6 +21,7 @@ public class TicketType<T> {
public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
public static final TicketType<Integer> POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type
+ public static final TicketType<Long> ENTITY_MOVEMENT = TicketType.create("entity_movement", Long::compareTo, 10*20); // Sakura - load chunks on movement
public static <T> TicketType<T> create(String name, Comparator<T> comparator) {
return new TicketType<>(name, comparator, 0L);
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index c4ace72f2eaf6a15c98490d18d3478b9ad5ed1a6..00b3e16d5465547e7d4f8126664fb7eda3b3c568 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -530,6 +530,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public boolean isPrimedTNT;
public boolean isFallingBlock;
// Sakura end - client visibility settings
+ // Sakura start - load chunks on movement
+ protected boolean loadChunks = false;
+
+ private int getExtraCollisionFlags() {
+ int flags = 0;
+
+ if (this.loadChunks) {
+ flags |= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_LOAD_CHUNKS;
+ flags |= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_ADD_TICKET;
+ }
+
+ return flags;
+ }
+ // Sakura end - load chunks on movement
public Entity(EntityType<?> entityType, Level level) {
this.type = entityType;
@@ -1469,7 +1483,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder(
this.level, (Entity)(Object)this, initialCollisionBox, potentialCollisionsVoxel, potentialCollisionsBB,
- ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, null
+ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | this.getExtraCollisionFlags(), null // Sakura - load chunks on movement
);
potentialCollisionsBB.addAll(entityAABBs);
final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB);
@@ -4961,13 +4975,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@Override
public boolean shouldBeSaved() {
return (this.removalReason == null || this.removalReason.shouldSave())
+ && !this.loadChunks // Sakura - load chunks on movement; this is used to check if the chunk the entity is in can be unloaded
&& !this.isPassenger()
&& (!this.isVehicle() || !((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this).moonrise$hasAnyPlayerPassengers()); // Paper - rewrite chunk system
}
@Override
public boolean isAlwaysTicking() {
- return false;
+ return this.loadChunks; // Sakura - load chunks on movement; always tick in unloaded & lazy chunks
}
public boolean mayInteract(ServerLevel level, BlockPos pos) {
diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java
index 5462a423be1317a306ca12ed10edcab636cd0c7f..ee5ec7a780488182e30134b29a20cc192609d64b 100644
--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -74,6 +74,7 @@ public class FallingBlockEntity extends Entity {
super(entityType, level);
this.heightParity = level.sakuraConfig().cannons.mechanics.fallingBlockParity; // Sakura - configure cannon mechanics
this.isFallingBlock = true; // Sakura - client visibility settings
+ this.loadChunks = level.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
}
public FallingBlockEntity(Level level, double x, double y, double z, BlockState state) {
diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java
index 8118911019f7fc81218a656e1ecbd7eada505741..e7b4efe35c20e11f130b5bce5c8c20390c65e0a4 100644
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -62,6 +62,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
super(entityType, level);
this.blocksBuilding = true;
this.isPrimedTNT = true; // Sakura - client visibility settings
+ this.loadChunks = level.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
}
public PrimedTnt(Level level, double x, double y, double z, @Nullable LivingEntity owner) {
diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
index 6d565b52552534ce9cacfc35ad1bf4adcb69eac3..9b42bd1afb9a6c1729cb56e3c232f46112ba57d3 100644
--- a/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -138,6 +138,17 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
private final int minSection;
private final int maxSection;
// Paper end - get block chunk optimisation
+ // Sakura start - load chunks on movement
+ private long lastMovementLoadTicket = 0;
+
+ public final boolean movementTicketNeedsUpdate() {
+ return net.minecraft.server.MinecraftServer.currentTick - this.lastMovementLoadTicket >= 100;
+ }
+
+ public final void updatedMovementTicket() {
+ this.lastMovementLoadTicket = net.minecraft.server.MinecraftServer.currentTick;
+ }
+ // Sakura end - load chunks on movement
public ChunkAccess(
ChunkPos chunkPos,

View File

@@ -0,0 +1,206 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cryptite <cryptite@gmail.com>
Date: Wed, 6 Oct 2021 11:03:01 -0500
Subject: [PATCH] (Slice) Packet obfuscation and reduction
Minecraft is overzealous about packet updates for Entities. In Loka's case, we want to reduce as many unnecessary
packet updates as possible. This patch is likely to be updated over and over in terms of reducing packet sends.
In summary, this patch creates the concept of a "foreignValue" of a packet's data. We treat packets in two ways:
1) The packet sent to the player itself (the normal way). This always has all of the values as usual.
2) The packet data as seen by any other (foreign) players.
This patch adds the ability to set a "foreignValue" for an entity value so as to obfuscate data received by other players.
The current packets modified/obfuscated are the following:
# This reduces the amount of health packet updates as well which is great for players in combat.
# Air level packets are sent PER-TICK, and as such a player with any change in air level will only spam themselves
# with packets instead of every single player within tracking distance
diff --git a/net/minecraft/network/syncher/SynchedEntityData.java b/net/minecraft/network/syncher/SynchedEntityData.java
index 3d90f9f1ac1bd281edf6bb0f93ea821657d5bd2f..6f3a6efe6624f9d4e500b2eee5d8aed3a6077e71 100644
--- a/net/minecraft/network/syncher/SynchedEntityData.java
+++ b/net/minecraft/network/syncher/SynchedEntityData.java
@@ -20,6 +20,30 @@ public class SynchedEntityData {
private final SyncedDataHolder entity;
private final SynchedEntityData.DataItem<?>[] itemsById;
private boolean isDirty;
+ // Slice start - packet obfuscation and reduction
+ private boolean isForeignDirty;
+
+ public final boolean isForeignDirty() {
+ return this.isForeignDirty;
+ }
+
+ @Nullable
+ public final List<SynchedEntityData.DataValue<?>> packForeignDirty(List<DataValue<?>> unpackedData) {
+ List<SynchedEntityData.DataValue<?>> list = null;
+ for (DataValue<?> dataItem : unpackedData) {
+ DataItem<?> item = this.itemsById[dataItem.id()];
+ if (item.isDirty(true)) {
+ item.setForeignDirty(false);
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+ list.add(item.copy(true));
+ }
+ }
+ this.isForeignDirty = false;
+ return list;
+ }
+ // Slice end - packet obfuscation and reduction
SynchedEntityData(SyncedDataHolder entity, SynchedEntityData.DataItem<?>[] itemsById) {
this.entity = entity;
@@ -58,6 +82,16 @@ public class SynchedEntityData {
}
public <T> void set(EntityDataAccessor<T> key, T value, boolean force) {
+ // Slice start - packet obfuscation and reduction
+ this.set(key, value, null, force);
+ }
+
+ public <T> void set(EntityDataAccessor<T> key, T value, T foreignValue) {
+ this.set(key, value, foreignValue, false);
+ }
+
+ public <T> void set(EntityDataAccessor<T> key, T value, T foreignValue, boolean force) {
+ // Slice end - packet obfuscation and reduction
SynchedEntityData.DataItem<T> item = this.getItem(key);
if (force || ObjectUtils.notEqual(value, item.getValue())) {
item.setValue(value);
@@ -65,6 +99,12 @@ public class SynchedEntityData {
item.setDirty(true);
this.isDirty = true;
}
+ // Slice start - packet obfuscation and reduction
+ if (foreignValue != null && ObjectUtils.notEqual(foreignValue, item.getForeignValue())) {
+ item.setForeignValue(foreignValue);
+ this.isForeignDirty = true;
+ }
+ // Slice end - packet obfuscation and reduction
}
// CraftBukkit start - add method from above
@@ -195,6 +235,38 @@ public class SynchedEntityData {
T value;
private final T initialValue;
private boolean dirty;
+ // Slice start - packet obfuscation and reduction
+ @Nullable T foreignValue = null;
+ private boolean foreignDirty = true;
+
+ public final void setForeignValue(T foreignValue) {
+ this.foreignValue = foreignValue;
+ this.foreignDirty = true;
+ }
+
+ public final @Nullable T getForeignValue() {
+ return this.foreignValue;
+ }
+
+ public final boolean isDirty(boolean foreign) {
+ if (foreign) {
+ //There must be a foreign value in order for this to be dirty, otherwise we consider this a normal
+ //value and check the normal dirty flag.
+ return this.foreignValue == null || this.foreignDirty;
+ }
+
+ return this.dirty;
+ }
+
+ public final void setForeignDirty(boolean dirty) {
+ this.foreignDirty = dirty;
+ }
+
+ public final SynchedEntityData.DataValue<T> copy(boolean foreign) {
+ return SynchedEntityData.DataValue.create(this.accessor, this.accessor.serializer()
+ .copy(foreign && this.foreignValue != null ? this.foreignValue : this.value));
+ }
+ // Slice end - packet obfuscation and reduction
public DataItem(EntityDataAccessor<T> accessor, T value) {
this.accessor = accessor;
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..8abe899d19434ad4c7cc6c1596bab16df7b14275 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -138,7 +138,7 @@ public class ServerEntity {
this.sendDirtyEntityData();
}
- if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) { // Paper - fix desync when a player is added to the tracker
+ if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isForeignDirty()) { // Slice - packet obfuscation and reduction // Paper - fix desync when a player is added to the tracker
byte b = Mth.packDegrees(this.entity.getYRot());
byte b1 = Mth.packDegrees(this.entity.getXRot());
boolean flag = Math.abs(b - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
@@ -404,7 +404,15 @@ public class ServerEntity {
List<SynchedEntityData.DataValue<?>> list = entityData.packDirty();
if (list != null) {
this.trackedDataValues = entityData.getNonDefaultValues();
- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
+ // Slice start - packet obfuscation and reduction
+ if (!(this.entity instanceof ServerPlayer)) {
+ list = entityData.packForeignDirty(list);
+ }
+
+ if (list != null) {
+ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
+ }
+ // Slice end - packet obfuscation and reduction
}
if (this.entity instanceof LivingEntity) {
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 00b3e16d5465547e7d4f8126664fb7eda3b3c568..7bbc4a982f442fdb9821221442737ae65e55289e 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -3495,7 +3495,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID);
return;
}
- this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount());
+ this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount(), getMaxAirSupply()); // Slice
// CraftBukkit end
}
diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java
index ee5ec7a780488182e30134b29a20cc192609d64b..4836be29ff5e87e4f2b9beb0d4a9943281ab5262 100644
--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -120,7 +120,7 @@ public class FallingBlockEntity extends Entity {
}
public void setStartPos(BlockPos startPos) {
- this.entityData.set(DATA_START_POS, startPos);
+ this.entityData.set(DATA_START_POS, startPos, BlockPos.ZERO); // Slice - packet obfuscation and reduction
}
public BlockPos getStartPos() {
diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java
index e7b4efe35c20e11f130b5bce5c8c20390c65e0a4..9e9463d62aa1618a4a749bb7e2636c9b090991e9 100644
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -241,7 +241,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
}
public void setFuse(int life) {
- this.entityData.set(DATA_FUSE_ID, life);
+ this.entityData.set(DATA_FUSE_ID, life, (life / 10) * 10); // Slice - packet obfuscation and reduction
}
public int getFuse() {
diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java
index ee6d5d3ccd55cffdeb96eeb77f648fd82feceb80..09209e5a9b61244a0877818c0841d19aee72522b 100644
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -673,7 +673,7 @@ public abstract class Player extends LivingEntity {
public void increaseScore(int score) {
int score1 = this.getScore();
- this.entityData.set(DATA_SCORE_ID, score1 + score);
+ this.entityData.set(DATA_SCORE_ID, score1 + score, 0); // Slice - packet obfuscation and reduction
}
public void startAutoSpinAttack(int ticks, float damage, ItemStack itemStack) {

View File

@@ -0,0 +1,160 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 19 Apr 2024 22:20:03 +0100
Subject: [PATCH] Optimise paper explosions
diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
index df584256e133bfc0b7effd56961f1b91c264c7bd..9f06e32101f494d94d9210210255d5d72ca4ff36 100644
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -88,7 +88,7 @@ public class ServerExplosion implements Explosion {
}
}
- CACHED_RAYS = rayCoords.toDoubleArray();
+ CACHED_RAYS = sortExplosionRays(rayCoords); // Sakura - optimise paper explosions
}
private static final int CHUNK_CACHE_SHIFT = 2;
@@ -307,6 +307,39 @@ public class ServerExplosion implements Explosion {
}
// Paper end - collisions optimisations
private final boolean consistentRadius; // Sakura - consistent explosion radius
+ // Sakura start - optimise paper explosions
+ /*
+ * Sort the explosion rays to better utilise the chunk and block cache.
+ * x + Vanilla Sorted
+ * z @ z 8 5
+ * - x 6 7 6 4
+ * 4 @ 5 7 @ 3
+ * 2 3 8 2
+ * 1 1
+ */
+ private static double[] sortExplosionRays(it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords) {
+ List<double[]> explosionRays = new ArrayList<>();
+
+ for (int i = 0; i < rayCoords.size(); i += 3) {
+ double[] ray = new double[3];
+ rayCoords.getElements(i, ray, 0, 3);
+ explosionRays.add(ray);
+ }
+
+ rayCoords.clear();
+ explosionRays.sort(java.util.Comparator.comparingDouble(vec -> {
+ double sign = Math.signum(vec[0]);
+ double dir = (sign - 1) / 2;
+ return sign + 8 + vec[2] * dir;
+ }));
+
+ double[] rays = new double[explosionRays.size() * 3];
+ for (int i = 0; i < explosionRays.size() * 3; i++) {
+ rays[i] = explosionRays.get(i / 3)[i % 3];
+ }
+ return rays;
+ }
+ // Sakura end - optimise paper explosions
public ServerExplosion(
ServerLevel level,
@@ -398,6 +431,12 @@ public class ServerExplosion implements Explosion {
initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
}
+ // Sakura start - optimise paper explosions
+ if (!this.interactsWithBlocks() || initialCache.resistance > (this.radius * 1.3f)) {
+ return ret;
+ }
+ // Sakura end - optimise paper explosions
+
// only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of
// a 16x16x16 cube
// we can cache the rays and their normals as well, so that we eliminate the excess iterations / checks and
@@ -477,16 +516,55 @@ public class ServerExplosion implements Explosion {
// Paper end - collision optimisations
}
- private void hurtEntities() {
- float f = this.radius * 2.0F;
+ // Sakura start - optimise paper explosions
+ protected final AABB getExplosionBounds(float f) {
int floor = Mth.floor(this.center.x - f - 1.0);
int floor1 = Mth.floor(this.center.x + f + 1.0);
int floor2 = Mth.floor(this.center.y - f - 1.0);
int floor3 = Mth.floor(this.center.y + f + 1.0);
int floor4 = Mth.floor(this.center.z - f - 1.0);
int floor5 = Mth.floor(this.center.z + f + 1.0);
- List <Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB(floor, floor2, floor4, floor1, floor3, floor5), entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
- for (Entity entity : list) { // Paper - used in loop
+ return new AABB(floor, floor2, floor4, floor1, floor3, floor5);
+ }
+
+ private void hurtEntities() {
+ float f = this.radius * 2.0F;
+
+ int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this.level);
+ int maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this.level);
+
+ int minChunkX = Mth.floor(this.center.x - f) >> 4;
+ int maxChunkX = Mth.floor(this.center.x + f) >> 4;
+ int minChunkY = Mth.clamp(Mth.floor(this.center.y - f) >> 4, minSection, maxSection);
+ int maxChunkY = Mth.clamp(Mth.floor(this.center.y + f) >> 4, minSection, maxSection);
+ int minChunkZ = Mth.floor(this.center.z - f) >> 4;
+ int maxChunkZ = Mth.floor(this.center.z + f) >> 4;
+
+ ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup entityLookup = this.level.moonrise$getEntityLookup();
+ for (int chunkX = minChunkX; chunkX <= maxChunkX; ++chunkX) {
+ for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; ++chunkZ) {
+ ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices chunk = entityLookup.getChunk(chunkX, chunkZ);
+ if (chunk == null) continue; // empty slice
+
+ for (int chunkY = minChunkY; chunkY <= maxChunkY; ++chunkY) {
+ this.impactEntities(f, chunk.getSectionEntities(chunkY));
+ }
+ }
+ }
+ }
+
+ protected final void impactEntities(float f, Entity[] entities) {
+ for (int i = 0; i < entities.length; i++) {
+ Entity entity = entities[i];
+ if (entity == null) break; // end of entity section
+ this.impactEntity(f, entity);
+ if (entity != entities[i]) i--; // entities can be removed mid-explosion
+ }
+ }
+
+ protected final void impactEntity(float f, Entity entity) {
+ if (entity.isAlive() && !entity.isSpectator() && (!this.excludeSourceFromDamage || entity != this.source)) { // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
+ // Sakura end - optimise paper explosions
if (!entity.ignoreExplosion(this)) {
double d = Math.sqrt(entity.distanceToSqr(this.center)) / f;
if (d <= 1.0) {
@@ -511,15 +589,16 @@ public class ServerExplosion implements Explosion {
// - Damaging EntityEnderDragon does nothing
// - EnderDragon hitbock always covers the other parts and is therefore always present
if (entity instanceof EnderDragonPart) {
- continue;
+ return; // Sakura - optimise paper explosions
}
entity.lastDamageCancelled = false;
if (entity instanceof EnderDragon) {
+ final AABB bounds = this.getExplosionBounds(f); // Sakura - optimise paper explosions
for (EnderDragonPart dragonPart : ((EnderDragon) entity).getSubEntities()) {
// Calculate damage separately for each EntityComplexPart
- if (list.contains(dragonPart)) {
+ if (dragonPart.getBoundingBox().intersects(bounds)) { // Sakura - optimise paper explosions
dragonPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
}
}
@@ -528,7 +607,7 @@ public class ServerExplosion implements Explosion {
}
if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
- continue;
+ return; // Sakura - optimise paper explosions
}
// CraftBukkit end
}

View File

@@ -0,0 +1,48 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Wed, 16 Aug 2023 22:34:49 +0100
Subject: [PATCH] Store Entity Data/State
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 7bbc4a982f442fdb9821221442737ae65e55289e..3671003b9fdf787fc5095d12df9ee2f15bd998f1 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -544,6 +544,25 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return flags;
}
// Sakura end - load chunks on movement
+ // Sakura start - store entity data/state
+ private me.samsuik.sakura.entity.EntityState entityState = null;
+
+ public final Vec3 stuckSpeedMultiplier() {
+ return this.stuckSpeedMultiplier;
+ }
+
+ public final void storeEntityState() {
+ this.entityState = me.samsuik.sakura.entity.EntityState.of(this);
+ }
+
+ public final me.samsuik.sakura.entity.EntityState entityState() {
+ return this.entityState;
+ }
+
+ public final boolean compareState(Entity to) {
+ return to.entityState() != null && to.entityState().comparePositionAndMotion(this);
+ }
+ // Sakura end - store entity data/state
public Entity(EntityType<?> entityType, Level level) {
this.type = entityType;
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index 3ba2228fd83bcd9af5f46d71409abe9fd2328f7e..e4584cd58d84f2b64748dfec7a1aa69fca119021 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -1510,6 +1510,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public <T extends Entity> void guardEntityTick(Consumer<T> consumerEntity, T entity) {
try {
+ entity.storeEntityState(); // Sakura - store entity data/state
consumerEntity.accept(entity);
} catch (Throwable var6) {
// Paper start - Prevent block entity and entity crashes

View File

@@ -0,0 +1,343 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 9 Sep 2023 18:39:15 +0100
Subject: [PATCH] Merge Cannon Entities
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index f1f322f623756f0c0a06a4207a12765c9623e151..ae1fad06d85de83f53884449cff21fc0ae62bf97 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1750,6 +1750,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
profilerFiller.pop();
serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions
serverLevel.explosionPositions.clear(); // Sakura - client visibility settings
+ serverLevel.mergeHandler.expire(currentTick); // Sakura - merge cannon entities
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 30454e69d603c3de3e2ecb5498d79c25bcc15a07..83bd1ddec7edb09e9a28eead178d7d07cbde8749 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -810,6 +810,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
+ final Entity[] previousEntity = new Entity[1]; // Sakura - merge cannon entities
this.entityTickList
.forEach(
entity -> {
@@ -828,6 +829,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
entity.stopRiding();
}
+ // Sakura start - merge cannon entities
+ Entity previous = previousEntity[0];
+ if (this.mergeHandler.tryMerge(entity, previous)) {
+ return;
+ } else {
+ previousEntity[0] = entity;
+ }
+ // Sakura end - merge cannon entities
+
profilerFiller.push("tick");
this.guardEntityTick(this::tickNonPassenger, entity);
profilerFiller.pop();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 3671003b9fdf787fc5095d12df9ee2f15bd998f1..2a617bd6d5d14cd69b149d6c5f82f8b2c3bc2d5d 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -563,6 +563,23 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return to.entityState() != null && to.entityState().comparePositionAndMotion(this);
}
// Sakura end - store entity data/state
+ // Sakura start - merge cannon entities
+ public final void updateBukkitHandle(Entity entity) {
+ if (this.bukkitEntity != null) {
+ this.bukkitEntity.setHandle(entity);
+ }
+ this.bukkitEntity = entity.getBukkitEntity();
+ }
+
+ public final long getPackedOriginPosition() {
+ org.bukkit.util.Vector origin = this.getOriginVector();
+ if (origin != null) {
+ return BlockPos.asLong(origin.getBlockX(), origin.getBlockY(), origin.getBlockZ());
+ } else {
+ return Long.MIN_VALUE;
+ }
+ }
+ // Sakura end - merge cannon entities
public Entity(EntityType<?> entityType, Level level) {
this.type = entityType;
@@ -4964,6 +4981,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (this.removalReason != Entity.RemovalReason.UNLOADED_TO_CHUNK) { this.getPassengers().forEach(Entity::stopRiding); } // Paper - rewrite chunk system
this.levelCallback.onRemove(removalReason);
this.onRemoval(removalReason);
+ // Sakura start - merge cannon entities
+ if (removalReason == RemovalReason.DISCARDED) {
+ this.level.mergeHandler.removeEntity(this);
+ }
+ // Sakura end - merge cannon entities
// Paper start - Folia schedulers
if (!(this instanceof ServerPlayer) && removalReason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
// Players need to be special cased, because they are regularly removed from the world
diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java
index 4836be29ff5e87e4f2b9beb0d4a9943281ab5262..1d9afcf995ee734f13803e26956439e5c3450f44 100644
--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -54,7 +54,7 @@ import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.event.entity.EntityRemoveEvent;
// CraftBukkit end
-public class FallingBlockEntity extends Entity {
+public class FallingBlockEntity extends Entity implements me.samsuik.sakura.entity.merge.MergeableEntity { // Sakura - merge cannon entities
private static final Logger LOGGER = LogUtils.getLogger();
public BlockState blockState = Blocks.SAND.defaultBlockState();
public int time;
@@ -70,11 +70,62 @@ public class FallingBlockEntity extends Entity {
public boolean autoExpire = true; // Paper - Expand FallingBlock API
public boolean heightParity; // Sakura - falling block height parity api
+ // Sakura start - merge cannon entities
+ private final me.samsuik.sakura.entity.merge.MergeEntityData mergeData = new me.samsuik.sakura.entity.merge.MergeEntityData(this);
+
+ @Override
+ public final me.samsuik.sakura.entity.merge.MergeEntityData getMergeEntityData() {
+ return this.mergeData;
+ }
+
+ @Override
+ public final boolean isSafeToMergeInto(me.samsuik.sakura.entity.merge.MergeableEntity entity, boolean ticksLived) {
+ return entity instanceof FallingBlockEntity fbe
+ && fbe.blockState.equals(this.blockState)
+ && (!ticksLived || fbe.time - 1 == this.time);
+ }
+
+ @Override
+ public final void respawnEntity(int count) {
+ while (count-- >= 1) {
+ // Unlike PrimedTnt we have to try respawn each stacked entity
+ FallingBlockEntity fallingBlock = new FallingBlockEntity(EntityType.FALLING_BLOCK, this.level());
+
+ // Try to stack the falling block
+ this.entityState().apply(fallingBlock);
+ fallingBlock.blockState = this.blockState;
+ fallingBlock.spawnReason = this.spawnReason;
+ fallingBlock.time = this.time - 1;
+ fallingBlock.tick();
+
+ // If you horizontal stack into a moving piston block this condition will be met.
+ if (!fallingBlock.isRemoved()) {
+ this.mergeData.setCount(count + 1);
+ fallingBlock.storeEntityState();
+ fallingBlock.entityState().apply(this);
+ break;
+ } else if (count == 0) {
+ this.discard(EntityRemoveEvent.Cause.DESPAWN);
+ }
+ }
+ }
+
+ @Override
+ public @Nullable ItemEntity spawnAtLocation(ServerLevel level, net.minecraft.world.level.ItemLike item) { // may be overridden by plugins
+ ItemEntity itemEntity = null;
+ for (int i = 0; i < this.mergeData.getCount(); ++i) {
+ itemEntity = super.spawnAtLocation(level, item);
+ }
+ return itemEntity;
+ }
+ // Sakura end - merge cannon entities
+
public FallingBlockEntity(EntityType<? extends FallingBlockEntity> entityType, Level level) {
super(entityType, level);
this.heightParity = level.sakuraConfig().cannons.mechanics.fallingBlockParity; // Sakura - configure cannon mechanics
this.isFallingBlock = true; // Sakura - client visibility settings
this.loadChunks = level.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
+ this.mergeData.setMergeLevel(level.sakuraConfig().cannons.mergeLevel); // Sakura - merge cannon entities
}
public FallingBlockEntity(Level level, double x, double y, double z, BlockState state) {
@@ -222,6 +273,7 @@ public class FallingBlockEntity extends Entity {
return;
}
// CraftBukkit end
+ if (this.respawnEntity()) return; // Sakura - merge cannon entities
if (this.level().setBlock(blockPos, this.blockState, 3)) {
((ServerLevel)this.level())
.getChunkSource()
@@ -328,6 +380,7 @@ public class FallingBlockEntity extends Entity {
compound.putBoolean("CancelDrop", this.cancelDrop);
if (!this.autoExpire) compound.putBoolean("Paper.AutoExpire", false); // Paper - Expand FallingBlock API
+ compound.putInt("merge_count", this.mergeData.getCount()); // Sakura - merge cannon entities
}
@Override
@@ -360,6 +413,11 @@ public class FallingBlockEntity extends Entity {
this.autoExpire = compound.getBoolean("Paper.AutoExpire");
}
// Paper end - Expand FallingBlock API
+ // Sakura start - merge cannon entities
+ if (compound.contains("merge_count", 3)) {
+ this.mergeData.setCount(compound.getInt("merge_count"));
+ }
+ // Sakura end - merge cannon entities
}
public void setHurtsEntities(float fallDamagePerDistance, int fallDamageMax) {
diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java
index 9e9463d62aa1618a4a749bb7e2636c9b090991e9..b378b4c4930c4ebd55795591aca173fd1fee46c9 100644
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -33,7 +33,7 @@ import org.bukkit.event.entity.EntityRemoveEvent;
import org.bukkit.event.entity.ExplosionPrimeEvent;
// CraftBukkit end
-public class PrimedTnt extends Entity implements TraceableEntity {
+public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sakura.entity.merge.MergeableEntity { // Sakura - merge cannon entities
private static final EntityDataAccessor<Integer> DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<BlockState> DATA_BLOCK_STATE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.BLOCK_STATE);
private static final int DEFAULT_FUSE_TIME = 80;
@@ -58,11 +58,48 @@ public class PrimedTnt extends Entity implements TraceableEntity {
public float explosionPower = 4.0F;
public boolean isIncendiary = false; // CraftBukkit - add field
+ // Sakura start - merge cannon entities
+ private final me.samsuik.sakura.entity.merge.MergeEntityData mergeData = new me.samsuik.sakura.entity.merge.MergeEntityData(this);
+
+ @Override
+ public final me.samsuik.sakura.entity.merge.MergeEntityData getMergeEntityData() {
+ return this.mergeData;
+ }
+
+ @Override
+ public final boolean isSafeToMergeInto(me.samsuik.sakura.entity.merge.MergeableEntity entity, boolean ticksLived) {
+ return entity instanceof PrimedTnt tnt
+ && tnt.getFuse() + 1 == this.getFuse()
+ // required to prevent issues with powdered snow
+ && (tnt.entityState().fallDistance() == this.fallDistance
+ || tnt.entityState().fallDistance() > 2.5f && this.fallDistance > 2.5f);
+ }
+
+ @Override
+ public final void respawnEntity(int count) {
+ PrimedTnt tnt = new PrimedTnt(EntityType.TNT, this.level());
+ tnt.updateBukkitHandle(this); // update handle for plugins
+ while (count-- > 1) {
+ this.setFuse(100); // Prevent unwanted explosions while ticking
+
+ // Cause an explosion to affect this entity
+ tnt.setPos(this.position());
+ tnt.setDeltaMovement(this.getDeltaMovement());
+ this.entityState().apply(this);
+ tnt.explode();
+ this.storeEntityState();
+
+ this.tick();
+ }
+ }
+ // Sakura end - merge cannon entities
+
public PrimedTnt(EntityType<? extends PrimedTnt> entityType, Level level) {
super(entityType, level);
this.blocksBuilding = true;
this.isPrimedTNT = true; // Sakura - client visibility settings
this.loadChunks = level.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
+ this.mergeData.setMergeLevel(level.sakuraConfig().cannons.mergeLevel); // Sakura - merge cannon entities
}
public PrimedTnt(Level level, double x, double y, double z, @Nullable LivingEntity owner) {
@@ -142,6 +179,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (i <= 0) {
// CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event
//this.discard();
+ this.respawnEntity(); // Sakura - merge cannon entities
if (!this.level().isClientSide) {
this.explode();
}
@@ -212,6 +250,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (this.explosionPower != 4.0F) {
compound.putFloat("explosion_power", this.explosionPower);
}
+ compound.putInt("merge_count", this.mergeData.getCount()); // Sakura - merge cannon entities
}
@Override
@@ -224,6 +263,11 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (compound.contains("explosion_power", 99)) {
this.explosionPower = Mth.clamp(compound.getFloat("explosion_power"), 0.0F, 128.0F);
}
+ // Sakura start - merge cannon entities
+ if (compound.contains("merge_count", 3)) {
+ this.mergeData.setCount(compound.getInt("merge_count"));
+ }
+ // Sakura end - merge cannon entities
}
@Nullable
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index e4584cd58d84f2b64748dfec7a1aa69fca119021..a4d7c95514c3db5799efe178efee796413d1bac8 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -837,6 +837,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z);
}
// Paper end - optimise random ticking
+ public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities
protected Level(
WritableLevelData levelData,
diff --git a/net/minecraft/world/level/block/BasePressurePlateBlock.java b/net/minecraft/world/level/block/BasePressurePlateBlock.java
index 108c1d23bf80777b943edfa0b5585ebb928540a7..69d490c79e30fb42da69bbd804ecaea7b88fe7b0 100644
--- a/net/minecraft/world/level/block/BasePressurePlateBlock.java
+++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java
@@ -91,7 +91,7 @@ public abstract class BasePressurePlateBlock extends Block {
}
private void checkPressed(@Nullable Entity entity, Level level, BlockPos pos, BlockState state, int currentSignal) {
- int signalStrength = this.getSignalStrength(level, pos);
+ int signalStrength = this.getSignalStrength(level, pos, currentSignal == 0); // Sakura - merge cannon entities
boolean flag = currentSignal > 0;
boolean flag1 = signalStrength > 0;
@@ -168,6 +168,12 @@ public abstract class BasePressurePlateBlock extends Block {
// CraftBukkit end
}
+ // Sakura start - merge cannon entities
+ protected int getSignalStrength(Level world, BlockPos pos, boolean entityInside) {
+ return this.getSignalStrength(world, pos);
+ }
+ // Sakura end - merge cannon entities
+
protected abstract int getSignalStrength(Level level, BlockPos pos);
protected abstract int getSignalForState(BlockState state);
diff --git a/net/minecraft/world/level/block/WeightedPressurePlateBlock.java b/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
index 0ad494a861c04aeacb0620000e306cfab813fdde..c49044097fa8d3294de10a681717cd424e6c1078 100644
--- a/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
+++ b/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
@@ -40,6 +40,11 @@ public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
@Override
protected int getSignalStrength(Level level, BlockPos pos) {
+ // Sakura start - merge cannon entities
+ return this.getSignalStrength(level, pos, false);
+ }
+ protected final int getSignalStrength(Level level, BlockPos pos, boolean entityInside) {
+ // Sakura end - merge cannon entities
// CraftBukkit start
// int min = Math.min(getEntityCount(level, TOUCH_AABB.move(pos), Entity.class), this.maxWeight);
int min = 0;
@@ -55,7 +60,7 @@ public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
// We only want to block turning the plate on if all events are cancelled
if (!cancellable.isCancelled()) {
- min++;
+ min += !entityInside && entity instanceof me.samsuik.sakura.entity.merge.MergeableEntity mergeEntity ? mergeEntity.getMergeEntityData().getCount() : 1; // Sakura - merge cannon entities
}
}

View File

@@ -0,0 +1,50 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Tue, 21 Sep 2021 23:54:25 +0100
Subject: [PATCH] Client Visibility Settings
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index dab4d38a2408af5f8f415e5a916845bf08ec536c..7f8bbddc7b5b0c8d13a2772b91df44d6e99aadf3 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2385,6 +2385,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
handle.keepLevel = data.getBoolean("keepLevel");
}
}
+
+ // Sakura start - client visibility settings; load from nbt
+ if (nbttagcompound.contains("sakura", 10)) {
+ CompoundTag sakuraTag = nbttagcompound.getCompound("sakura");
+ this.getHandle().visibilitySettings.loadData(sakuraTag);
+ }
+ // Sakura end - client visibility settings; load from nbt
}
public void setExtraData(CompoundTag nbttagcompound) {
@@ -2414,6 +2421,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
paper.putLong("LastLogin", handle.loginTime);
paper.putLong("LastSeen", System.currentTimeMillis());
// Paper end
+ // Sakura start - client visibility settings; save to nbt
+ CompoundTag sakuraTag = nbttagcompound.getCompound("sakura");
+ this.getHandle().visibilitySettings.saveData(sakuraTag);
+ nbttagcompound.put("sakura", sakuraTag);
+ // Sakura end - client visibility settings; save to nbt
}
@Override
@@ -3085,6 +3097,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.getHandle().allowsListing();
}
+ // Sakura start - client visibility settings; api
+ @Override
+ public final me.samsuik.sakura.player.visibility.VisibilitySettings getVisibility() {
+ return this.getHandle().visibilitySettings;
+ }
+ // Sakura end - client visibility settings; api
+
// Paper start
@Override
public net.kyori.adventure.text.Component displayName() {

View File

@@ -0,0 +1,72 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Sat, 9 Sep 2023 18:39:15 +0100
Subject: [PATCH] Merge Cannon Entities
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
index 22b6016a8d6828b2b10c028b24fd160b3b9f9f59..ad924326b6bfd01b719096ff53ed4b8e513e25af 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
@@ -26,6 +26,28 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock {
}
// Sakura end - falling block height parity api
+ // Sakura start - merge cannon entities
+ @Override
+ public @org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel getMergeLevel() {
+ return this.getHandle().getMergeEntityData().getMergeLevel();
+ }
+
+ @Override
+ public void setMergeLevel(@org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel level) {
+ this.getHandle().getMergeEntityData().setMergeLevel(level);
+ }
+
+ @Override
+ public int getStacked() {
+ return this.getHandle().getMergeEntityData().getCount();
+ }
+
+ @Override
+ public void setStacked(int stacked) {
+ this.getHandle().getMergeEntityData().setCount(stacked);
+ }
+ // Sakura end - merge cannon entities
+
@Override
public FallingBlockEntity getHandle() {
return (FallingBlockEntity) this.entity;
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
index a61aec087fa7cec27a803668bdc1b9e6eb336755..c6f36ab2368d0e2e4555d5f8edc0132dcb61a53c 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
@@ -12,6 +12,28 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed {
super(server, entity);
}
+ // Sakura start - merge cannon entities
+ @Override
+ public @org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel getMergeLevel() {
+ return this.getHandle().getMergeEntityData().getMergeLevel();
+ }
+
+ @Override
+ public void setMergeLevel(@org.jetbrains.annotations.NotNull me.samsuik.sakura.entity.merge.MergeLevel level) {
+ this.getHandle().getMergeEntityData().setMergeLevel(level);
+ }
+
+ @Override
+ public int getStacked() {
+ return this.getHandle().getMergeEntityData().getCount();
+ }
+
+ @Override
+ public void setStacked(int stacked) {
+ this.getHandle().getMergeEntityData().setCount(stacked);
+ }
+ // Sakura end - merge cannon entities
+
@Override
public float getYield() {
return this.getHandle().explosionPower;

View File

@@ -1,6 +1,6 @@
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -115,6 +_,18 @@
@@ -116,6 +_,18 @@
throw new AssertionError("Unknown entity " + (entity == null ? null : entity.getClass()));
}

View File

@@ -2,6 +2,9 @@ package me.samsuik.sakura.command;
import me.samsuik.sakura.command.subcommands.ConfigCommand;
import me.samsuik.sakura.command.subcommands.TPSCommand;
import me.samsuik.sakura.command.subcommands.FPSCommand;
import me.samsuik.sakura.command.subcommands.VisualCommand;
import me.samsuik.sakura.player.visibility.VisibilityTypes;
import net.minecraft.server.MinecraftServer;
import org.bukkit.command.Command;
@@ -14,6 +17,9 @@ public final class SakuraCommands {
COMMANDS.put("sakura", new SakuraCommand("sakura"));
COMMANDS.put("config", new ConfigCommand("config"));
COMMANDS.put("tps", new TPSCommand("tps"));
COMMANDS.put("fps", new FPSCommand("fps"));
COMMANDS.put("tntvisibility", new VisualCommand(VisibilityTypes.TNT, "tnttoggle"));
COMMANDS.put("sandvisibility", new VisualCommand(VisibilityTypes.SAND, "sandtoggle"));
}
public static void registerCommands(MinecraftServer server) {

View File

@@ -0,0 +1,24 @@
package me.samsuik.sakura.command.subcommands;
import me.samsuik.sakura.command.BaseSubCommand;
import me.samsuik.sakura.player.visibility.VisibilityGui;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
@DefaultQualifier(NonNull.class)
public final class FPSCommand extends BaseSubCommand {
private final VisibilityGui visibilityGui = new VisibilityGui();
public FPSCommand(String name) {
super(name);
}
@Override
public void execute(CommandSender sender, String[] args) {
if (sender instanceof Player player) {
this.visibilityGui.showTo(player);
}
}
}

View File

@@ -0,0 +1,41 @@
package me.samsuik.sakura.command.subcommands;
import me.samsuik.sakura.command.BaseSubCommand;
import me.samsuik.sakura.configuration.GlobalConfiguration;
import me.samsuik.sakura.player.visibility.VisibilitySettings;
import me.samsuik.sakura.player.visibility.VisibilityState;
import me.samsuik.sakura.player.visibility.VisibilityType;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import java.util.Arrays;
@DefaultQualifier(NonNull.class)
public final class VisualCommand extends BaseSubCommand {
private final VisibilityType type;
public VisualCommand(VisibilityType type, String... aliases) {
super(type.key() + "visibility");
this.setAliases(Arrays.asList(aliases));
this.type = type;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
return;
}
VisibilitySettings settings = player.getVisibility();
VisibilityState state = settings.toggle(type);
String stateName = (state == VisibilityState.ON) ? "Enabled" : "Disabled";
player.sendRichMessage(GlobalConfiguration.get().messages.fpsSettingChange,
Placeholder.unparsed("name", this.type.key()),
Placeholder.unparsed("state", stateName)
);
}
}

View File

@@ -0,0 +1,41 @@
package me.samsuik.sakura.entity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.NullMarked;
import java.util.Optional;
@NullMarked
public record EntityState(Vec3 position, Vec3 momentum, AABB bb, Vec3 stuckSpeed, Optional<BlockPos> supportingPos, boolean onGround, float fallDistance) {
public static EntityState of(Entity entity) {
return new EntityState(
entity.position(), entity.getDeltaMovement(), entity.getBoundingBox(),
entity.stuckSpeedMultiplier(), entity.mainSupportingBlockPos,
entity.onGround(), entity.fallDistance
);
}
public void apply(Entity entity) {
entity.setPos(this.position);
entity.setDeltaMovement(this.momentum);
entity.setBoundingBox(this.bb);
entity.makeStuckInBlock(Blocks.AIR.defaultBlockState(), this.stuckSpeed);
entity.onGround = this.onGround;
entity.mainSupportingBlockPos = this.supportingPos;
entity.fallDistance = this.fallDistance;
}
public void applyEntityPosition(Entity entity) {
entity.setPos(this.position);
entity.setBoundingBox(this.bb);
}
public boolean comparePositionAndMotion(Entity entity) {
return entity.position().equals(this.position)
&& entity.getDeltaMovement().equals(this.momentum);
}
}

View File

@@ -0,0 +1,76 @@
package me.samsuik.sakura.entity.merge;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class EntityMergeHandler {
private final TrackedMergeHistory trackedHistory = new TrackedMergeHistory();
/**
* Tries to merge the provided entities using the {@link MergeStrategy}.
*
* @param previous the last entity to tick
* @param entity the entity being merged
* @return success
*/
public boolean tryMerge(@Nullable Entity entity, @Nullable Entity previous) {
if (entity instanceof MergeableEntity mergeEntity && previous instanceof MergeableEntity) {
MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
MergeStrategy strategy = MergeStrategy.from(mergeEntityData.getMergeLevel());
Entity into = strategy.mergeEntity(entity, previous, this.trackedHistory);
if (into instanceof MergeableEntity intoEntity && !into.isRemoved() && mergeEntity.isSafeToMergeInto(intoEntity, strategy.trackHistory())) {
return this.mergeEntity(mergeEntity, intoEntity);
}
}
return false;
}
/**
* Stores the merged data of the provided entities if the {@link MergeStrategy} requires it.
*
* @param entity provided entity
*/
public void removeEntity(@Nullable Entity entity) {
if (entity instanceof MergeableEntity mergeEntity) {
MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData();
MergeStrategy strategy = MergeStrategy.from(mergeEntityData.getMergeLevel());
if (mergeEntityData.hasMerged() && strategy.trackHistory()) {
this.trackedHistory.trackHistory(entity, mergeEntityData);
}
}
}
/**
* Called every tick and provided the current server tick to remove any unneeded merge history.
*
* @param tick server tick
*/
public void expire(int tick) {
if (tick % 200 == 0) {
this.trackedHistory.expire(tick);
}
}
/**
* Merges the first entity into the second. The entity merge count can be retrieved through the {@link MergeEntityData}.
* <p>
* This method also updates the bukkit handle so that plugins reference the first entity after the second entity has been removed.
*
* @param mergeEntity the first entity
* @param into the entity to merge into
* @return if successful
*/
public boolean mergeEntity(@NotNull MergeableEntity mergeEntity, @NotNull MergeableEntity into) {
MergeEntityData entities = mergeEntity.getMergeEntityData();
MergeEntityData mergeInto = into.getMergeEntityData();
mergeInto.mergeWith(entities); // merge entities together
// discard the entity and update the bukkit handle
Entity nmsEntity = (Entity) mergeEntity;
nmsEntity.discard();
nmsEntity.updateBukkitHandle((Entity) into);
return true;
}
}

View File

@@ -0,0 +1,16 @@
package me.samsuik.sakura.entity.merge;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
public interface MergeCondition {
default MergeCondition and(@NotNull MergeCondition condition) {
return (e,c,t) -> this.accept(e,c,t) && condition.accept(e,c,t);
}
default MergeCondition or(@NotNull MergeCondition condition) {
return (e,c,t) -> this.accept(e,c,t) || condition.accept(e,c,t);
}
boolean accept(@NotNull Entity entity, int attempts, long sinceCreation);
}

View File

@@ -0,0 +1,52 @@
package me.samsuik.sakura.entity.merge;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public final class MergeEntityData {
private final Entity entity;
private List<MergeEntityData> connected = new ObjectArrayList<>();
private int count = 1;
private MergeLevel mergeLevel = MergeLevel.NONE;
public MergeEntityData(Entity entity) {
this.entity = entity;
}
public void mergeWith(@NotNull MergeEntityData mergeEntityData) {
this.connected.add(mergeEntityData);
this.connected.addAll(mergeEntityData.connected);
this.count += mergeEntityData.getCount();
mergeEntityData.setCount(0);
}
public LongOpenHashSet getOriginPositions() {
LongOpenHashSet positions = new LongOpenHashSet();
this.connected.forEach(entityData -> positions.add(entityData.entity.getPackedOriginPosition()));
return positions;
}
public boolean hasMerged() {
return !this.connected.isEmpty() && this.count != 0;
}
public void setMergeLevel(MergeLevel mergeLevel) {
this.mergeLevel = mergeLevel;
}
public MergeLevel getMergeLevel() {
return mergeLevel;
}
public void setCount(int count) {
this.count = count;
}
public int getCount() {
return this.count;
}
}

View File

@@ -0,0 +1,118 @@
package me.samsuik.sakura.entity.merge;
import me.samsuik.sakura.utils.collections.FixedSizeCustomObjectTable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.entity.EntityTickList;
import org.jetbrains.annotations.NotNull;
public interface MergeStrategy {
/**
* If this merge strategy requires the merge history to be tracked.
*
* @return should track history
*/
boolean trackHistory();
/**
* Tries to merge the first entity into another entity.
* <p>
* The first entity should always be positioned right after the second entity in the
* {@link EntityTickList}. This method should only
* be called before the first entity and after the second entity has ticked.
*
* @param entity current entity
* @param previous last entity to tick
* @return success
*/
Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory);
/**
* Gets the {@link MergeStrategy} for the {@link MergeLevel}.
*
* @param level provided level
* @return strategy
*/
static MergeStrategy from(MergeLevel level) {
return switch (level) {
case NONE -> None.INSTANCE;
case STRICT -> Strict.INSTANCE;
case LENIENT -> Lenient.INSTANCE;
case SPAWN -> Spawn.INSTANCE;
};
}
final class None implements MergeStrategy {
private static final None INSTANCE = new None();
@Override
public boolean trackHistory() {
return false;
}
@Override
public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
return null;
}
}
final class Strict implements MergeStrategy {
private static final Strict INSTANCE = new Strict();
@Override
public boolean trackHistory() {
return false;
}
@Override
public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
return entity.compareState(previous) ? previous : null;
}
}
final class Lenient implements MergeStrategy {
private static final Lenient INSTANCE = new Lenient();
private final FixedSizeCustomObjectTable<Entity> entityTable = new FixedSizeCustomObjectTable<>(512, entity -> {
return entity.blockPosition().hashCode();
});
@Override
public boolean trackHistory() {
return true;
}
@Override
public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
if (entity.compareState(previous)) {
return previous;
}
Entity nextEntity = this.entityTable.getAndWrite(entity);
if (nextEntity == null || !nextEntity.level().equals(entity.level())) {
return null;
}
return mergeHistory.hasPreviousMerged(entity, nextEntity) && entity.compareState(nextEntity) ? nextEntity : null;
}
}
final class Spawn implements MergeStrategy {
private static final Spawn INSTANCE = new Spawn();
private static final MergeCondition CONDITION = (e, shots, time) -> (shots > 16 || time >= 200);
@Override
public boolean trackHistory() {
return true;
}
@Override
public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) {
final Entity mergeInto;
if (entity.tickCount == 1 && mergeHistory.hasPreviousMerged(entity, previous) && mergeHistory.hasMetCondition(previous, CONDITION)) {
mergeInto = previous;
} else {
mergeInto = entity.compareState(previous) ? previous : null;
}
return mergeInto;
}
}
}

View File

@@ -0,0 +1,22 @@
package me.samsuik.sakura.entity.merge;
import org.jetbrains.annotations.NotNull;
public interface MergeableEntity {
@NotNull MergeEntityData getMergeEntityData();
boolean isSafeToMergeInto(@NotNull MergeableEntity entity, boolean ticksLived);
default boolean respawnEntity() {
MergeEntityData mergeData = this.getMergeEntityData();
int count = mergeData.getCount();
if (count > 1) {
mergeData.setCount(0);
this.respawnEntity(count);
return true;
}
return false;
}
void respawnEntity(int count);
}

View File

@@ -0,0 +1,83 @@
package me.samsuik.sakura.entity.merge;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import me.samsuik.sakura.configuration.WorldConfiguration.Cannons.Mechanics.TNTSpread;
import me.samsuik.sakura.utils.TickExpiry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.FallingBlockEntity;
import org.jetbrains.annotations.NotNull;
public final class TrackedMergeHistory {
private final Long2ObjectMap<PositionHistory> historyMap = new Long2ObjectOpenHashMap<>();
public boolean hasPreviousMerged(@NotNull Entity entity, @NotNull Entity into) {
PositionHistory positions = this.getHistory(into);
return positions != null && positions.has(entity.getPackedOriginPosition());
}
public void trackHistory(@NotNull Entity entity, @NotNull MergeEntityData mergeEntityData) {
long originPosition = entity.getPackedOriginPosition();
PositionHistory positions = this.historyMap.computeIfAbsent(originPosition, p -> new PositionHistory());
LongOpenHashSet originPositions = mergeEntityData.getOriginPositions();
boolean createHistory = positions.hasTimePassed(160);
if (createHistory && (entity instanceof FallingBlockEntity || entity.level().sakuraConfig().cannons.mechanics.tntSpread == TNTSpread.ALL)) {
originPositions.forEach(pos -> this.historyMap.put(pos, positions));
}
positions.trackPositions(originPositions, !createHistory);
}
public boolean hasMetCondition(@NotNull Entity entity, MergeCondition condition) {
PositionHistory positions = this.getHistory(entity);
return positions != null && positions.hasMetConditions(entity, condition);
}
private PositionHistory getHistory(Entity entity) {
long originPosition = entity.getPackedOriginPosition();
return this.historyMap.get(originPosition);
}
public void expire(int tick) {
this.historyMap.values().removeIf(p -> p.expiry().isExpired(tick));
}
private static final class PositionHistory {
private final LongOpenHashSet positions = new LongOpenHashSet();
private final TickExpiry expiry = new TickExpiry(MinecraftServer.currentTick, 200);
private final long created = MinecraftServer.currentTick;
private int cycles = 0;
public TickExpiry expiry() {
return this.expiry;
}
public boolean has(long position) {
this.expiry.refresh(MinecraftServer.currentTick);
return this.positions.contains(position);
}
public void trackPositions(LongOpenHashSet positions, boolean retain) {
if (retain) {
this.positions.retainAll(positions);
} else {
this.positions.addAll(positions);
}
this.cycles++;
}
public boolean hasMetConditions(@NotNull Entity entity, @NotNull MergeCondition condition) {
this.expiry.refresh(MinecraftServer.currentTick);
return condition.accept(entity, this.cycles, this.timeSinceCreation());
}
public boolean hasTimePassed(int ticks) {
return this.timeSinceCreation() > ticks;
}
private long timeSinceCreation() {
return MinecraftServer.currentTick - this.created;
}
}
}

View File

@@ -41,7 +41,7 @@ public final class LocalConfigManager implements LocalStorageHandler {
int regionX = x >> this.regionExponent;
int regionZ = z >> this.regionExponent;
long regionPos = ChunkPos.asLong(regionX, regionZ);
List<LocalRegion> regions = this.smallRegions.get(regionPos);
List<LocalRegion> regions = this.smallRegions.getOrDefault(regionPos, List.of());
for (LocalRegion region : Iterables.concat(regions, this.largeRegions)) {
if (region.contains(x, z)) {
return Optional.of(region);

View File

@@ -0,0 +1,44 @@
package me.samsuik.sakura.player.gui;
import me.samsuik.sakura.player.gui.components.GuiComponent;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public abstract class FeatureGui {
private final int size;
private final Component title;
public FeatureGui(int size, Component title) {
this.size = size;
this.title = title;
}
protected abstract void fillInventory(Inventory inventory);
protected abstract void afterFill(Player player, FeatureGuiInventory inventory);
public final void showTo(Player bukkitPlayer) {
FeatureGuiInventory featureInventory = new FeatureGuiInventory(this, this.size, this.title);
this.fillInventory(featureInventory.getInventory());
this.afterFill(bukkitPlayer, featureInventory);
bukkitPlayer.openInventory(featureInventory.getInventory());
}
@ApiStatus.Internal
public static void clickEvent(InventoryClickEvent event) {
Inventory clicked = event.getClickedInventory();
if (clicked != null && clicked.getHolder(false) instanceof FeatureGuiInventory featureInventory) {
event.setCancelled(true);
for (GuiComponent component : featureInventory.getComponents().reversed()) {
if (component.interaction(event, featureInventory)) {
break;
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
package me.samsuik.sakura.player.gui;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import me.samsuik.sakura.player.gui.components.GuiComponent;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jspecify.annotations.NullMarked;
import java.util.Collection;
import java.util.Optional;
@NullMarked
public final class FeatureGuiInventory implements InventoryHolder {
private final Inventory inventory;
private final FeatureGui gui;
private final Multimap<NamespacedKey, GuiComponent> componentsUnderKey = HashMultimap.create();
private final Object2ObjectMap<GuiComponent, NamespacedKey> componentKeys = new Object2ObjectLinkedOpenHashMap<>();
public FeatureGuiInventory(FeatureGui gui, int size, Component component) {
this.inventory = Bukkit.createInventory(this, size, component);
this.gui = gui;
}
@Override
public Inventory getInventory() {
return this.inventory;
}
public FeatureGui getGui() {
return this.gui;
}
public ImmutableList<GuiComponent> getComponents() {
return ImmutableList.copyOf(this.componentKeys.keySet());
}
public ImmutableList<GuiComponent> findComponents(NamespacedKey key) {
return ImmutableList.copyOf(this.componentsUnderKey.get(key));
}
public Optional<GuiComponent> findFirst(NamespacedKey key) {
Collection<GuiComponent> components = this.componentsUnderKey.get(key);
return components.stream().findFirst();
}
public void removeComponents(NamespacedKey key) {
Collection<GuiComponent> removed = this.componentsUnderKey.removeAll(key);
for (GuiComponent component : removed) {
this.componentKeys.remove(component);
}
}
public void addComponent(GuiComponent component, NamespacedKey key) {
Preconditions.checkArgument(!this.componentKeys.containsKey(component), "component has already been added");
this.componentKeys.put(component, key);
this.componentsUnderKey.put(key, component);
this.inventoryUpdate(component);
}
public void removeComponent(GuiComponent component) {
NamespacedKey key = this.componentKeys.remove(component);
this.componentsUnderKey.remove(key, component);
}
public void replaceComponent(GuiComponent component, GuiComponent replacement) {
NamespacedKey key = this.componentKeys.remove(component);
Preconditions.checkNotNull(key, "component does not exist");
this.componentKeys.put(replacement, key);
this.componentsUnderKey.remove(key, component);
this.componentsUnderKey.put(key, replacement);
this.inventoryUpdate(replacement);
}
public void removeAllComponents() {
this.componentKeys.clear();
this.componentsUnderKey.clear();
}
private void inventoryUpdate(GuiComponent component) {
component.creation(this.inventory);
}
}

View File

@@ -0,0 +1,19 @@
package me.samsuik.sakura.player.gui;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class ItemStackUtil {
public static ItemStack itemWithBlankName(Material material) {
return itemWithName(material, Component.empty());
}
public static ItemStack itemWithName(Material material, Component component) {
ItemStack item = new ItemStack(material);
item.editMeta(m -> m.itemName(component));
return item;
}
}

View File

@@ -0,0 +1,10 @@
package me.samsuik.sakura.player.gui.components;
import me.samsuik.sakura.player.gui.FeatureGuiInventory;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface GuiClickEvent {
void doSomething(InventoryClickEvent event, FeatureGuiInventory inventory);
}

View File

@@ -0,0 +1,13 @@
package me.samsuik.sakura.player.gui.components;
import me.samsuik.sakura.player.gui.FeatureGuiInventory;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface GuiComponent {
boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory);
void creation(Inventory inventory);
}

View File

@@ -0,0 +1,34 @@
package me.samsuik.sakura.player.gui.components;
import me.samsuik.sakura.player.gui.FeatureGuiInventory;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class ItemButton implements GuiComponent {
private final ItemStack bukkitItem;
private final int slot;
private final GuiClickEvent whenClicked;
public ItemButton(ItemStack bukkitItem, int slot, GuiClickEvent whenClicked) {
this.bukkitItem = bukkitItem;
this.slot = slot;
this.whenClicked = whenClicked;
}
@Override
public boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory) {
if (event.getSlot() == this.slot) {
this.whenClicked.doSomething(event, featureInventory);
return true;
}
return false;
}
@Override
public void creation(Inventory inventory) {
inventory.setItem(this.slot, this.bukkitItem);
}
}

View File

@@ -0,0 +1,44 @@
package me.samsuik.sakura.player.gui.components;
import com.google.common.base.Preconditions;
import me.samsuik.sakura.player.gui.FeatureGuiInventory;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jspecify.annotations.NullMarked;
import java.util.Collections;
import java.util.List;
@NullMarked
public final class ItemSwitch implements GuiComponent {
private final List<ItemStack> items;
private final int slot;
private final int selected;
private final GuiClickEvent whenClicked;
public ItemSwitch(List<ItemStack> items, int slot, int selected, GuiClickEvent whenClicked) {
Preconditions.checkArgument(!items.isEmpty());
this.items = Collections.unmodifiableList(items);
this.slot = slot;
this.selected = selected;
this.whenClicked = whenClicked;
}
@Override
public boolean interaction(InventoryClickEvent event, FeatureGuiInventory featureInventory) {
if (this.slot == event.getSlot()) {
int next = (this.selected + 1) % this.items.size();
ItemSwitch itemSwitch = new ItemSwitch(this.items, this.slot, next, this.whenClicked);
featureInventory.replaceComponent(this, itemSwitch);
this.whenClicked.doSomething(event, featureInventory);
return true;
}
return false;
}
@Override
public void creation(Inventory inventory) {
inventory.setItem(this.slot, this.items.get(this.selected));
}
}

View File

@@ -0,0 +1,65 @@
package me.samsuik.sakura.player.visibility;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NonNull;
public final class PlayerVisibilitySettings implements VisibilitySettings {
private static final String SETTINGS_COMPOUND_TAG = "clientVisibilitySettings";
private final Reference2ObjectMap<VisibilityType, VisibilityState> visibilityStates = new Reference2ObjectOpenHashMap<>();
@Override
public @NonNull VisibilityState get(@NonNull VisibilityType type) {
VisibilityState state = this.visibilityStates.get(type);
return state != null ? state : type.getDefault();
}
@Override
public @NotNull VisibilityState set(@NonNull VisibilityType type, @NonNull VisibilityState state) {
if (type.isDefault(state)) {
this.visibilityStates.remove(type);
} else {
this.visibilityStates.put(type, state);
}
return state;
}
@Override
public @NonNull VisibilityState currentState() {
int modifiedCount = this.visibilityStates.size();
if (modifiedCount == 0) {
return VisibilityState.ON;
} else if (modifiedCount != VisibilityTypes.types().size()) {
return VisibilityState.MODIFIED;
} else {
return VisibilityState.OFF;
}
}
@Override
public boolean playerModified() {
return !this.visibilityStates.isEmpty();
}
public void loadData(@NonNull CompoundTag tag) {
if (!tag.contains(SETTINGS_COMPOUND_TAG, CompoundTag.TAG_COMPOUND)) {
return;
}
CompoundTag settingsTag = tag.getCompound(SETTINGS_COMPOUND_TAG);
for (VisibilityType type : VisibilityTypes.types()) {
if (settingsTag.contains(type.key(), CompoundTag.TAG_STRING)) {
VisibilityState state = VisibilityState.valueOf(settingsTag.getString(type.key()));
this.visibilityStates.put(type, state);
}
}
}
public void saveData(@NonNull CompoundTag tag) {
CompoundTag settingsTag = new CompoundTag();
this.visibilityStates.forEach((t, s) -> settingsTag.putString(t.key(), s.name()));
tag.put(SETTINGS_COMPOUND_TAG, settingsTag);
}
}

View File

@@ -0,0 +1,96 @@
package me.samsuik.sakura.player.visibility;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import me.samsuik.sakura.configuration.GlobalConfiguration;
import me.samsuik.sakura.player.gui.FeatureGui;
import me.samsuik.sakura.player.gui.FeatureGuiInventory;
import me.samsuik.sakura.player.gui.components.ItemButton;
import me.samsuik.sakura.player.gui.components.ItemSwitch;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jspecify.annotations.NullMarked;
import static me.samsuik.sakura.player.gui.ItemStackUtil.itemWithBlankName;
@NullMarked
public final class VisibilityGui extends FeatureGui {
private static final NamespacedKey TOGGLE_BUTTON_KEY = new NamespacedKey("sakura", "toggle_button");
private static final NamespacedKey MENU_ITEMS_KEY = new NamespacedKey("sakura", "menu_items");
public VisibilityGui() {
super(45, Component.text("FPS Settings"));
}
@Override
protected void fillInventory(Inventory inventory) {
for (int slot = 0; slot < inventory.getSize(); ++slot) {
// x, y from top left of the inventory
int x = slot % 9;
int y = slot / 9;
// from center
int rx = x - 4;
int ry = y - 2;
double d = Math.sqrt(rx * rx + ry * ry);
if (d <= 3.25) {
inventory.setItem(slot, itemWithBlankName(GlobalConfiguration.get().fps.material));
} else if (x % 8 == 0) {
inventory.setItem(slot, itemWithBlankName(Material.BLACK_STAINED_GLASS_PANE));
} else {
inventory.setItem(slot, itemWithBlankName(Material.WHITE_STAINED_GLASS_PANE));
}
}
}
@Override
protected void afterFill(Player player, FeatureGuiInventory inventory) {
VisibilitySettings settings = player.getVisibility();
IntArrayFIFOQueue slots = this.availableSlots();
this.updateToggleButton(settings, player, inventory);
for (VisibilityType type : VisibilityTypes.types()) {
VisibilityState state = settings.get(type);
int index = type.states().indexOf(state);
int slot = slots.dequeueInt();
ItemSwitch itemSwitch = new ItemSwitch(
VisibilityGuiItems.GUI_ITEMS.get(type),
slot, index,
(e, inv) -> {
settings.cycle(type);
this.updateToggleButton(settings, player, inv);
}
);
inventory.addComponent(itemSwitch, MENU_ITEMS_KEY);
}
}
private void updateToggleButton(VisibilitySettings settings, Player player, FeatureGuiInventory inventory) {
inventory.removeComponents(TOGGLE_BUTTON_KEY);
VisibilityState settingsState = settings.currentState();
ItemButton button = new ItemButton(
VisibilityGuiItems.TOGGLE_BUTTON_ITEMS.get(settingsState),
(2 * 9) + 8,
(e, inv) -> {
settings.toggleAll();
inventory.removeAllComponents();
this.afterFill(player, inv);
}
);
inventory.addComponent(button, TOGGLE_BUTTON_KEY);
}
private IntArrayFIFOQueue availableSlots() {
IntArrayFIFOQueue slots = new IntArrayFIFOQueue();
for (int row = 1; row < 4; ++row) {
for (int column = 3; column < 6; ++column) {
if ((column + row) % 2 == 0) {
slots.enqueue((row * 9) + column);
}
}
}
return slots;
}
}

View File

@@ -0,0 +1,55 @@
package me.samsuik.sakura.player.visibility;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import me.samsuik.sakura.player.gui.ItemStackUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.Locale;
public final class VisibilityGuiItems {
static final Reference2ObjectMap<VisibilityType, ImmutableList<ItemStack>> GUI_ITEMS = new Reference2ObjectOpenHashMap<>();
static final Reference2ObjectMap<VisibilityState, ItemStack> TOGGLE_BUTTON_ITEMS = new Reference2ObjectOpenHashMap<>();
static {
Reference2ObjectMap<VisibilityType, ItemStack> items = new Reference2ObjectOpenHashMap<>();
items.put(VisibilityTypes.TNT, ItemStackUtil.itemWithName(Material.TNT, Component.text("Tnt", NamedTextColor.RED)));
items.put(VisibilityTypes.SAND, ItemStackUtil.itemWithName(Material.SAND, Component.text("Sand", NamedTextColor.YELLOW)));
items.put(VisibilityTypes.EXPLOSIONS, ItemStackUtil.itemWithName(Material.COBWEB, Component.text("Explosions", NamedTextColor.WHITE)));
items.put(VisibilityTypes.SPAWNERS, ItemStackUtil.itemWithName(Material.SPAWNER, Component.text("Spawners", NamedTextColor.DARK_GRAY)));
items.put(VisibilityTypes.PISTONS, ItemStackUtil.itemWithName(Material.PISTON, Component.text("Pistons", NamedTextColor.GOLD)));
for (VisibilityType type : VisibilityTypes.types()) {
ItemStack item = items.get(type);
ImmutableList<ItemStack> stateItems = type.states().stream()
.map(s -> createItemForState(item, s))
.collect(ImmutableList.toImmutableList());
GUI_ITEMS.put(type, stateItems);
}
TOGGLE_BUTTON_ITEMS.put(VisibilityState.ON, ItemStackUtil.itemWithName(Material.GREEN_STAINED_GLASS_PANE, Component.text("Enabled", NamedTextColor.GREEN)));
TOGGLE_BUTTON_ITEMS.put(VisibilityState.MODIFIED, ItemStackUtil.itemWithName(Material.MAGENTA_STAINED_GLASS_PANE, Component.text("Modified", NamedTextColor.LIGHT_PURPLE)));
TOGGLE_BUTTON_ITEMS.put(VisibilityState.OFF, ItemStackUtil.itemWithName(Material.RED_STAINED_GLASS_PANE, Component.text("Disabled", NamedTextColor.RED)));
}
private static String lowercaseThenCapitalise(String name) {
String lowercaseName = name.toLowerCase(Locale.ENGLISH);
return StringUtils.capitalize(lowercaseName);
}
private static ItemStack createItemForState(ItemStack in, VisibilityState state) {
String capitalisedName = lowercaseThenCapitalise(state.name());
Component component = Component.text(" | " + capitalisedName, NamedTextColor.GRAY);
ItemStack itemCopy = in.clone();
itemCopy.editMeta(m -> m.itemName(m.itemName().append(component)));
return itemCopy;
}
}

Submodule sakura-server/src/minecraft/java updated: 7a738b2575...51af06117f