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..211b65246169785bce3c69ebc4ac9b4f39231605 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java @@ -0,0 +1,67 @@ +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(into, 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(long tick) { + if (tick % 200 == 0) { + this.trackedHistory.expire(tick); + } + } + + private 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..db215f9d624fb484059db1f86609847ac7a2be0b --- /dev/null +++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java @@ -0,0 +1,51 @@ +package me.samsuik.sakura.entity.merge; + +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; + +public final class MergeEntityData { + private final Entity entity; + private MergeEntityData linked; + private int count = 1; + private MergeLevel mergeLevel = MergeLevel.NONE; + + public MergeEntityData(Entity entity) { + this.entity = entity; + } + + public void mergeWith(@NotNull MergeEntityData mergeEntityData) { + this.linked = mergeEntityData; + this.count += mergeEntityData.getCount(); + mergeEntityData.setCount(0); + } + + public LongOpenHashSet getOriginPositions() { + LongOpenHashSet positions = new LongOpenHashSet(); + MergeEntityData next = this; + do { + positions.add(next.entity.getPackedOriginPosition()); + } while ((next = next.linked) != null); + return positions; + } + + public boolean hasMerged() { + return this.linked != null; + } + + 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..1ce8b8a54f80039c2f50496021494fbd0d81a82f --- /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. + *

+ * 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 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.hasMetCondition(entity, CONDITION) && mergeHistory.hasPreviousMerged(entity, previous)) { + 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..81b1aaa4d6718d92e4011501f55be2ab520198d9 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java @@ -0,0 +1,23 @@ +package me.samsuik.sakura.entity.merge; + +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; + +public interface MergeableEntity { + @NotNull MergeEntityData getMergeEntityData(); + + boolean isSafeToMergeInto(@NotNull Entity 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..c78bf4fc13da8238f59bde7de9f04642eba074ae --- /dev/null +++ b/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java @@ -0,0 +1,62 @@ +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.utils.objects.Expiry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; + +public final class TrackedMergeHistory { + private final Long2ObjectMap historyMap = new Long2ObjectOpenHashMap<>(); + + public boolean hasPreviousMerged(@NotNull Entity entity, @NotNull Entity into) { + long entityPosition = entity.getPackedOriginPosition(); + long intoPosition = into.getPackedOriginPosition(); + PositionHistory positions = this.historyMap.get(intoPosition); + return positions.has(entityPosition); + } + + public void trackHistory(@NotNull Entity entity, @NotNull MergeEntityData mergeEntityData) { + long originPosition = entity.getPackedOriginPosition(); + PositionHistory positions = this.historyMap.computeIfAbsent(originPosition, p -> new PositionHistory()); + positions.trackPositions(mergeEntityData.getOriginPositions()); + } + + public boolean hasMetCondition(@NotNull Entity entity, MergeCondition condition) { + long originPosition = entity.getPackedOriginPosition(); + PositionHistory positions = this.historyMap.get(originPosition); + return positions != null && positions.hasMetConditions(entity, condition); + } + + public void expire(long 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.currentTickLong, 200); + private final long created = MinecraftServer.currentTickLong; + private int cycles = 0; + + public Expiry expiry() { + return this.expiry; + } + + public boolean has(long position) { + this.expiry.refresh(MinecraftServer.currentTickLong); + return this.positions.contains(position); + } + + public void trackPositions(LongOpenHashSet positions) { + this.positions.addAll(positions); + this.cycles++; + } + + public boolean hasMetConditions(@NotNull Entity entity, @NotNull MergeCondition condition) { + long timeSinceCreation = MinecraftServer.currentTickLong - this.created; + return condition.accept(entity, this.cycles, timeSinceCreation); + } + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 0707bea1f98e36e85a15c7b63e9f967fd6683e9e..d1bd0536c902bacb8d57c1b7d51b571e906ee769 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1818,6 +1818,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { if (!entity.isRemoved()) { if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed @@ -776,6 +777,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. 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 49ae460ba5b871f085d67a0c4abdc0e7096ca3e1..023500526ab0a42bf7cbf21e1773cf5369aa2323 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -587,6 +587,23 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return to.entityState() != null && to.entityState().isCurrentState(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(); @@ -4874,6 +4891,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); + // 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 0891d4107c9a724522a7abbb6cf61298b18e42a6..19eed968884f79a7982d96ae8c3c378f00afbdf9 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 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(Entity 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(ItemLike item) { // may be overridden by plugins + ItemEntity itemEntity = null; + + for (int i = 0; i < this.mergeData.getCount(); ++i) { + itemEntity = super.spawnAtLocation(item); + } + + return itemEntity; + } + // Sakura end - merge cannon entities + public FallingBlockEntity(EntityType 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) { @@ -207,6 +260,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); @@ -326,6 +380,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 @@ -358,6 +413,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 888d018a8e73234332455b7d3700e70e1a50c5db..6fc9a352dbae57e867ae35f2a1f30eebe20073c7 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java @@ -30,7 +30,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 DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_BLOCK_STATE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.BLOCK_STATE); @@ -54,11 +54,48 @@ public class PrimedTnt extends Entity implements TraceableEntity { public float yield = 4; // CraftBukkit - add field 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(Entity 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 type, Level world) { super(type, world); 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) { @@ -118,6 +155,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(); } @@ -176,6 +214,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { protected void addAdditionalSaveData(CompoundTag nbt) { nbt.putShort("fuse", (short) this.getFuse()); nbt.put("block_state", NbtUtils.writeBlockState(this.getBlockState())); + nbt.putInt("merge_count", this.mergeData.getCount()); // Sakura - merge cannon entities } @Override @@ -184,6 +223,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 } diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index dd8e5c0a6197158b376e8de8930f660fc4771e54..ed43b7b1d7782e75c6b03cab5b75b282e5d96077 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -696,6 +696,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } // Paper end - optimise random ticking public final it.unimi.dsi.fastutil.longs.Long2IntMap minimalTNT = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); // Sakura - visibility api + 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 resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, 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 paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files// Paper - create paper world config & Anti-Xray this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot 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..ef0f1bb4c93288a97c17125089ea1a223cea04c5 100644 --- a/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WeightedPressurePlateBlock.java @@ -57,7 +57,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 += 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 dac3d34677688ac560bc1be2087a08479ef71b87..e71eb9e68c3b3c433a3fe4dfbe65f60214d6c225 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().yield;