9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-21 15:59:26 +00:00
Files
SakuraMC/patches/server/0016-Merge-Cannon-Entities.patch
Samsuik 9a7b139975 Updated Upstream (1.21.4 Paper)
Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@c0a3d51 Start update, apply API patches
PaperMC/Paper@172c7dc Work
PaperMC/Paper@ab9a3db More work
PaperMC/Paper@c60e47f More more work
PaperMC/Paper@bd55e32 More more more work
PaperMC/Paper@5265287 More more more more work
PaperMC/Paper@4601dc9 Some fixes, start updating CustomModelData API
PaperMC/Paper@2331dad Even more work
PaperMC/Paper@dc74c6f moonrise
PaperMC/Paper@d7d2f88 Apply remaining patches, fix API
PaperMC/Paper@f863bb7 Update generated classes
PaperMC/Paper@71a4ef8 Set java launcher for api generate task
PaperMC/Paper@b8aeecb Compilation fixes
PaperMC/Paper@6c35392 Tests succeed (by removing one)
PaperMC/Paper@b0603da Fix jd gson version, move back mc util diff
PaperMC/Paper@e2dd1d5 Add back post_teleport chunk ticket
PaperMC/Paper@7045b2a Update DataConverter
PaperMC/Paper@65633e3 Update Moonrise
2024-12-04 00:31:13 +00:00

819 lines
37 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <40902469+Samsuik@users.noreply.github.com>
Date: Sat, 9 Sep 2023 18:39:15 +0100
Subject: [PATCH] Merge Cannon Entities
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java b/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..917c2d4be95098a091ad19fb3e99f074188f00f9
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java
@@ -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;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c0c224bc30c9c75d4b82508661117ce16197680
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java
@@ -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);
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java
new file mode 100644
index 0000000000000000000000000000000000000000..b13e3dcc333bcb53b4493e7087765fcca0a88604
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java
@@ -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;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b490ddd7b98a41b14f67d0d754d89b5ef645d66
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java
@@ -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;
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java b/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..3061c3a48f7c68f64a3348b9583f4b41c16429a9
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java
@@ -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);
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java b/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc68bccf135bdcc7b3db7976e452ccae01e03b89
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java
@@ -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.objects.Expiry;
+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 Expiry expiry = new Expiry(MinecraftServer.currentTick, 200);
+ private final long created = MinecraftServer.currentTick;
+ private int cycles = 0;
+
+ public Expiry 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;
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 733541e91b0064d50de6c5c0985e9c472685c20c..c73aa5fd7da18219d1da63a0973f397316ba4210 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1905,6 +1905,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
worldserver.localConfig().expire(currentTick); // Sakura - add local config
worldserver.explosionPositions.clear(); // Sakura - client visibility settings
+ worldserver.mergeHandler.expire(currentTick); // Sakura - merge cannon entities
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index e1f0da0b20ec310470160718338920bbd5b088de..1e940c5b9ce60dd1dd7ec03e83766e26d1112d73 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -807,6 +807,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
+ Entity[] previousEntity = new Entity[1]; // Sakura - merge cannon entities
this.entityTickList.forEach((entity) -> {
if (!entity.isRemoved()) {
if (!tickratemanager.isEntityFrozen(entity)) {
@@ -824,6 +825,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
+
gameprofilerfiller.push("tick");
this.guardEntityTick(this::tickNonPassenger, entity);
gameprofilerfiller.pop();
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 316ab62995a7804cf85a2cb986b22e15cd22b01c..fd1d02604a1e46545c7f2c8ad89968279e2fe1bf 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -605,6 +605,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<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
@@ -5233,6 +5250,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(entity_removalreason);
this.onRemoval(entity_removalreason);
+ // Sakura start - merge cannon entities
+ if (entity_removalreason == RemovalReason.DISCARDED) {
+ this.level.mergeHandler.removeEntity(this);
+ }
+ // Sakura end - merge cannon entities
// Paper start - Folia schedulers
if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
// Players need to be special cased, because they are regularly removed from the world
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 81d02da6afd4b77c0ca60e9c8c5100ce6988753c..7dc5e5d91bb91c86ddca52a462903e8452396090 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -57,7 +57,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;
@@ -73,6 +73,58 @@ public class FallingBlockEntity extends Entity {
protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
public boolean autoExpire = true; // Paper - Expand FallingBlock 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);
+ }
+ }
+ }
+
+ @Nullable
+ public ItemEntity spawnAtLocation(ServerLevel 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> type, Level world) {
super(type, world);
this.blockState = Blocks.SAND.defaultBlockState();
@@ -80,6 +132,7 @@ public class FallingBlockEntity extends Entity {
this.fallDamageMax = 40;
this.isFallingBlock = true; // Sakura
this.loadChunks = world.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
+ this.mergeData.setMergeLevel(world.sakuraConfig().cannons.mergeLevel); // Sakura - merge cannon entities
}
public FallingBlockEntity(Level world, double x, double y, double z, BlockState block) {
@@ -222,6 +275,7 @@ public class FallingBlockEntity extends Entity {
return;
}
// CraftBukkit end
+ if (this.respawnEntity()) return; // Sakura - merge cannon entities
if (this.level().setBlock(blockposition, this.blockState, 3)) {
((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockposition, this.level().getBlockState(blockposition)));
this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
@@ -342,6 +396,7 @@ public class FallingBlockEntity extends Entity {
nbt.putBoolean("CancelDrop", this.cancelDrop);
if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API
+ nbt.putInt("merge_count", this.mergeData.getCount()); // Sakura - merge cannon entities
}
@Override
@@ -374,6 +429,11 @@ public class FallingBlockEntity extends Entity {
this.autoExpire = nbt.getBoolean("Paper.AutoExpire");
}
// Paper end - Expand FallingBlock API
+ // Sakura start - merge cannon entities
+ if (nbt.contains("merge_count", 3)) {
+ this.mergeData.setCount(nbt.getInt("merge_count"));
+ }
+ // Sakura end - merge cannon entities
}
public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) {
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 46b729ecf0c58bdbe7a4717e73b098dcffd910f1..f7d8e2ba0ee9b8dd27f20a3e75992b107d07fbf0 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/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);
@@ -59,12 +59,49 @@ public class PrimedTnt extends Entity implements TraceableEntity {
public float explosionPower;
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> type, Level world) {
super(type, world);
this.explosionPower = 4.0F;
this.blocksBuilding = true;
this.isPrimedTNT = true; // Sakura
this.loadChunks = world.sakuraConfig().cannons.loadChunks; // Sakura - load chunks on movement
+ this.mergeData.setMergeLevel(world.sakuraConfig().cannons.mergeLevel); // Sakura - merge cannon entities
}
public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) {
@@ -125,6 +162,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();
}
@@ -185,7 +223,7 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (this.explosionPower != 4.0F) {
nbt.putFloat("explosion_power", this.explosionPower);
}
-
+ nbt.putInt("merge_count", this.mergeData.getCount()); // Sakura - merge cannon entities
}
@Override
@@ -194,6 +232,11 @@ public class PrimedTnt extends Entity implements TraceableEntity {
if (nbt.contains("block_state", 10)) {
this.setBlockState(NbtUtils.readBlockState(this.level().holderLookup(Registries.BLOCK), nbt.getCompound("block_state")));
}
+ // Sakura start - merge cannon entities
+ if (nbt.contains("merge_count", 3)) {
+ this.mergeData.setCount(nbt.getInt("merge_count"));
+ }
+ // Sakura end - merge cannon entities
if (nbt.contains("explosion_power", 99)) {
this.explosionPower = Mth.clamp(nbt.getFloat("explosion_power"), 0.0F, 128.0F);
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index c69c04451c61bcc32e539baae160f05e3418841f..8f4a0912a5b5f17c22d2b4c0ef39e1c5566fa5f7 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -842,6 +842,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 worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.function.Supplier<me.samsuik.sakura.configuration.WorldConfiguration> sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files // Paper - create paper world config & Anti-Xray
// Paper start - getblock optimisations - cache world height/sections
diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
index 9afa811579ac2e556b5c5c23b3b49587439dfadc..c2eb63de04fc48bd2cc1aad8d9cba272c0829c80 100644
--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
@@ -89,7 +89,7 @@ public abstract class BasePressurePlateBlock extends Block {
}
private void checkPressed(@Nullable Entity entity, Level world, BlockPos pos, BlockState state, int output) {
- int j = this.getSignalStrength(world, pos);
+ int j = this.getSignalStrength(world, pos, output == 0); // Sakura - merge cannon entities
boolean flag = output > 0;
boolean flag1 = j > 0;
@@ -171,6 +171,12 @@ public abstract class BasePressurePlateBlock extends Block {
})); // CraftBukkit
}
+ // 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 world, BlockPos pos);
protected abstract int getSignalForState(BlockState state);
diff --git a/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
index 05bf23bd9ec951840ffceb68638458de02ad0063..f9c800e912ce966dba4f1026f2d5b702ae6549cd 100644
--- a/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
@@ -42,6 +42,11 @@ public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
@Override
protected int getSignalStrength(Level world, BlockPos pos) {
+ // Sakura start - merge cannon entities
+ return this.getSignalStrength(world, pos, false);
+ }
+ protected final int getSignalStrength(Level world, BlockPos pos, boolean entityInside) {
+ // Sakura end - merge cannon entities
// CraftBukkit start
// int i = Math.min(getEntityCount(world, BlockPressurePlateWeighted.TOUCH_AABB.move(blockposition), Entity.class), this.maxWeight);
int i = 0;
@@ -57,7 +62,7 @@ public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
// We only want to block turning the plate on if all events are cancelled
if (!cancellable.isCancelled()) {
- i++;
+ i += !entityInside && entity instanceof me.samsuik.sakura.entity.merge.MergeableEntity mergeEntity ? mergeEntity.getMergeEntityData().getCount() : 1; // Sakura - 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 1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69..55f67c2ca07eca0d3e1522eebbb4ce37704bdd04 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
@@ -14,6 +14,28 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock {
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 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;