mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-22 16:29:16 +00:00
749 lines
34 KiB
Diff
749 lines
34 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..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.
|
|
+ * <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.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<PositionHistory> 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 564278107e38b2a1adb03dab4c92a6ed20fd1d4e..b0e95e1dbf3248101d63bd2c0f9ba05e720623c6 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -1817,6 +1817,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
|
|
worldserver.localConfig().expire(currentTickLong); // Sakura - add local config
|
|
worldserver.minimalTNT.clear(); // Sakura - visibility api
|
|
+ worldserver.mergeHandler.expire(currentTickLong); // 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 4d0938df1604c3faca3997680a957dec3f07083b..df0465e4e073af7cbcee4939175d8f8c4b280396 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -747,6 +747,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
|
|
|
|
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
|
|
this.timings.entityTick.startTiming(); // Spigot
|
|
+ Entity[] previousEntity = new Entity[1]; // Sakura - merge cannon entities
|
|
this.entityTickList.forEach((entity) -> {
|
|
if (!entity.isRemoved()) {
|
|
if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed
|
|
@@ -766,6 +767,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 1fa332238999fd45b20bc2be2d6284329da909b6..d044262b2e1363151b8d076a9e3855db2a27254c 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();
|
|
@@ -4928,6 +4945,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 0458d965ca266606f922bf1b03b729810a3948a4..b873f6fce87afb048128d642f7588c18fcc6f7f5 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(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<? 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) {
|
|
@@ -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 af9bd6822f60ee46ff5cc173bc8bd76d13020a80..bdbee5ce0483a4fc4173c6ccf98d6a7a57f48867 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<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);
|
|
@@ -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<? extends PrimedTnt> 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 4c3c6a04155fa4a0ea81beaa5bf2a2f53ab5dbbc..48cfc7b3358ec2a663d31af3638eda7334474241 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -654,6 +654,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
|
|
}
|
|
// Paper end - optimise collisions
|
|
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<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> 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<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, Supplier<me.samsuik.sakura.configuration.WorldConfiguration> 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;
|