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..dbd38e3b5b32c324eff6875269f09131425796a4 --- /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}. + *

+ * 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 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..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..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..2c2a26fdb3081ced574070e5e6f3a1b4c09b578d --- /dev/null +++ b/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java @@ -0,0 +1,73 @@ +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) { + 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(); + boolean createHistory = !this.historyMap.containsKey(originPosition); + PositionHistory positions = this.historyMap.computeIfAbsent(originPosition, p -> new PositionHistory()); + LongOpenHashSet originPositions = mergeEntityData.getOriginPositions(); + if (createHistory) { + 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) { + long timeSinceCreation = MinecraftServer.currentTick - 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 567293a46e28e49096ddc1823473a6f7ada1c355..300579b17dfa21cda7100d232ed275d9fdae220d 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1901,6 +1901,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { if (!entity.isRemoved()) { if (!tickratemanager.isEntityFrozen(entity)) { @@ -823,6 +824,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 d9e236f8ec8735512d3bf02f07472b9be702bc9d..0b1eeefc0ff36665ffbba536e9944d74da0d94b0 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -648,6 +648,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(); @@ -5239,6 +5256,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 35d8422ea3bcdc52db6a7f6702fd593346b30c2c..f0617e9d71fa47dfc0566f31d85b8a05ba3b16fc 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(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 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 25a25b5c6497c9823a41e6bd8fd22f0841377c62..6b739e820af6333c1ff3a4b87154d4c00c9a5559 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 DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT); private static final EntityDataAccessor 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 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 49b01f78529f067eee65b14e7906b9f1de768f06..41a6c621dc26fe4bcb254d0465588c9227832758 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -841,6 +841,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 resourcekey, RegistryAccess iregistrycustom, Holder 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 paperWorldConfigCreator, java.util.function.Supplier 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;