diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java index 40a66b0..7a8e3cb 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/EntityMergeHandler.java @@ -1,9 +1,11 @@ package me.samsuik.sakura.entity.merge; +import me.samsuik.sakura.entity.merge.strategy.MergeStrategy; import net.minecraft.world.entity.Entity; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public final class EntityMergeHandler { private final TrackedMergeHistory trackedHistory = new TrackedMergeHistory(); @@ -17,7 +19,7 @@ public final class EntityMergeHandler { 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()); + MergeStrategy strategy = MergeStrategy.from(mergeEntityData.mergeLevel); 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); @@ -35,7 +37,7 @@ public final class EntityMergeHandler { public void removeEntity(@Nullable Entity entity) { if (entity instanceof MergeableEntity mergeEntity) { MergeEntityData mergeEntityData = mergeEntity.getMergeEntityData(); - MergeStrategy strategy = MergeStrategy.from(mergeEntityData.getMergeLevel()); + MergeStrategy strategy = MergeStrategy.from(mergeEntityData.mergeLevel); if (mergeEntityData.hasMerged() && strategy.trackHistory()) { this.trackedHistory.trackHistory(entity, mergeEntityData); } @@ -60,7 +62,7 @@ public final class EntityMergeHandler { * @param into the entity to merge into * @return if successful */ - public boolean mergeEntity(@NotNull MergeableEntity mergeEntity, @NotNull MergeableEntity into) { + public boolean mergeEntity(MergeableEntity mergeEntity, MergeableEntity into) { MergeEntityData entities = mergeEntity.getMergeEntityData(); MergeEntityData mergeInto = into.getMergeEntityData(); mergeInto.mergeWith(entities); // merge entities together diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java index 6c0c224..07a7e44 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java @@ -1,16 +1,17 @@ package me.samsuik.sakura.entity.merge; import net.minecraft.world.entity.Entity; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; +@NullMarked public interface MergeCondition { - default MergeCondition and(@NotNull MergeCondition condition) { + default MergeCondition and(MergeCondition condition) { return (e,c,t) -> this.accept(e,c,t) && condition.accept(e,c,t); } - default MergeCondition or(@NotNull MergeCondition condition) { + default MergeCondition or(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); + boolean accept(Entity entity, int attempts, long sinceCreation); } diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java index 1c18e1f..395c198 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java @@ -3,15 +3,16 @@ 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 org.jspecify.annotations.NullMarked; import java.util.List; +@NullMarked public final class MergeEntityData { private final Entity entity; private final List connected = new ObjectArrayList<>(); - private int count = 1; - private MergeLevel mergeLevel = MergeLevel.NONE; + public int count = 1; + public MergeLevel mergeLevel = MergeLevel.NONE; public MergeEntityData(Entity entity) { this.entity = entity; @@ -23,12 +24,17 @@ public final class MergeEntityData { } } - public void mergeWith(@NotNull MergeEntityData mergeEntityData) { + public void mergeWith(MergeEntityData mergeEntityData) { this.connected.add(mergeEntityData); this.connected.addAll(mergeEntityData.connected); - this.count += mergeEntityData.getCount(); + this.count += mergeEntityData.count; mergeEntityData.updateEntityHandles(this.entity); - mergeEntityData.setCount(0); + mergeEntityData.count = 0; + mergeEntityData.connected.clear(); + } + + public boolean hasMerged() { + return !this.connected.isEmpty() && this.count != 0; } public LongOpenHashSet getOriginPositions() { @@ -36,24 +42,4 @@ public final class MergeEntityData { 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/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java deleted file mode 100644 index 72fb188..0000000 --- a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeStrategy.java +++ /dev/null @@ -1,122 +0,0 @@ -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; - } - - if (!mergeHistory.hasPreviousMerged(entity, previous)) { - this.entityTable.clear(); - } - - Entity nextEntity = this.entityTable.getAndWrite(entity); - if (nextEntity == null || entity == nextEntity || !nextEntity.level().equals(entity.level())) { - return null; - } - - return entity.compareState(nextEntity) ? nextEntity : null; - } - } - - final class Spawn implements MergeStrategy { - private static final Spawn INSTANCE = new Spawn(); - private static final MergeCondition CONDITION = (e, shots, time) -> (shots > 16 || time >= 200); - - @Override - public boolean trackHistory() { - return true; - } - - @Override - public Entity mergeEntity(@NotNull Entity entity, @NotNull Entity previous, @NotNull TrackedMergeHistory mergeHistory) { - final Entity mergeInto; - if (entity.tickCount == 1 && mergeHistory.hasPreviousMerged(entity, previous) && mergeHistory.hasMetCondition(previous, CONDITION)) { - mergeInto = previous; - } else { - mergeInto = entity.compareState(previous) ? previous : null; - } - return mergeInto; - } - } -} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java index 3061c3a..4e06192 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java @@ -1,18 +1,19 @@ package me.samsuik.sakura.entity.merge; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; +@NullMarked public interface MergeableEntity { - @NotNull MergeEntityData getMergeEntityData(); + MergeEntityData getMergeEntityData(); - boolean isSafeToMergeInto(@NotNull MergeableEntity entity, boolean ticksLived); + boolean isSafeToMergeInto(MergeableEntity entity, boolean ticksLived); - default boolean respawnEntity() { + default boolean tryToRespawnEntity() { MergeEntityData mergeData = this.getMergeEntityData(); - int count = mergeData.getCount(); - if (count > 1) { - mergeData.setCount(0); - this.respawnEntity(count); + int originalCount = mergeData.count; + if (originalCount > 1) { + mergeData.count = 0; + this.respawnEntity(originalCount); return true; } return false; diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java index 514c795..faffb4c 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/TrackedMergeHistory.java @@ -7,28 +7,36 @@ import me.samsuik.sakura.configuration.WorldConfiguration.Cannons.Mechanics.TNTS import me.samsuik.sakura.utils.TickExpiry; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.item.FallingBlockEntity; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public final class TrackedMergeHistory { private final Long2ObjectMap historyMap = new Long2ObjectOpenHashMap<>(); - public boolean hasPreviousMerged(@NotNull Entity entity, @NotNull Entity into) { + public boolean hasPreviouslyMergedAndMeetsCondition(Entity entity, Entity into, MergeCondition condition) { + return this.hasPreviouslyMerged(entity, into) && this.hasMetCondition(entity, condition); + } + + public boolean hasPreviouslyMerged(Entity entity, Entity into) { PositionHistory positions = this.getHistory(into, false); return positions != null && positions.hasPosition(entity); } - public boolean hasMetCondition(@NotNull Entity entity, MergeCondition condition) { + public boolean hasMetCondition(Entity entity, MergeCondition condition) { PositionHistory positions = this.getHistory(entity, false); return positions != null && positions.hasMetConditions(entity, condition); } private boolean shouldTrackAllPositions(Entity entity, MergeEntityData mergeEntityData) { return entity instanceof FallingBlockEntity - || mergeEntityData.getMergeLevel() == MergeLevel.LENIENT + || mergeEntityData.mergeLevel == MergeLevel.LENIENT || entity.level().sakuraConfig().cannons.mechanics.tntSpread == TNTSpread.ALL; } - public void trackHistory(@NotNull Entity entity, @NotNull MergeEntityData mergeEntityData) { + public void trackHistory(Entity entity, MergeEntityData mergeEntityData) { PositionHistory positions = this.getHistory(entity, true); LongOpenHashSet originPositions = mergeEntityData.getOriginPositions(); long gameTime = entity.level().getGameTime(); @@ -43,9 +51,12 @@ public final class TrackedMergeHistory { this.historyMap.values().removeIf(p -> p.expiry().isExpired(gameTime)); } + @Nullable + @Contract("_, false -> _; _, true -> !null") private PositionHistory getHistory(Entity entity, boolean create) { long originPosition = entity.getPackedOriginPosition(); PositionHistory history = this.historyMap.get(originPosition); + //noinspection ConstantValue if (create && history == null) { history = new PositionHistory(entity.level().getGameTime()); this.historyMap.put(originPosition, history); diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/LenientStrategy.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/LenientStrategy.java new file mode 100644 index 0000000..0b0a0fe --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/LenientStrategy.java @@ -0,0 +1,40 @@ +package me.samsuik.sakura.entity.merge.strategy; + +import me.samsuik.sakura.entity.merge.TrackedMergeHistory; +import me.samsuik.sakura.utils.collections.FixedSizeCustomObjectTable; +import net.minecraft.world.entity.Entity; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +final class LenientStrategy implements MergeStrategy { + static final LenientStrategy INSTANCE = new LenientStrategy(); + + private final FixedSizeCustomObjectTable entityTable = new FixedSizeCustomObjectTable<>(512, entity -> { + return entity.blockPosition().hashCode(); + }); + + @Override + public boolean trackHistory() { + return true; + } + + @Override + @Nullable + public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) { + if (entity.compareState(previous)) { + return previous; + } + + if (!mergeHistory.hasPreviouslyMerged(entity, previous)) { + this.entityTable.clear(); + } + + final Entity nextEntity = this.entityTable.getAndWrite(entity); + if (nextEntity == null || entity == nextEntity || !nextEntity.level().equals(entity.level())) { + return null; + } + + return entity.compareState(nextEntity) ? nextEntity : null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/MergeStrategy.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/MergeStrategy.java new file mode 100644 index 0000000..b7b727e --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/MergeStrategy.java @@ -0,0 +1,47 @@ +package me.samsuik.sakura.entity.merge.strategy; + +import me.samsuik.sakura.entity.merge.MergeLevel; +import me.samsuik.sakura.entity.merge.TrackedMergeHistory; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.entity.EntityTickList; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +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 + */ + @Nullable + Entity mergeEntity(Entity entity, Entity previous, 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 -> NoneStrategy.INSTANCE; + case STRICT -> StrictStrategy.INSTANCE; + case LENIENT -> LenientStrategy.INSTANCE; + case SPAWN -> SpawnStrategy.INSTANCE; + }; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/NoneStrategy.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/NoneStrategy.java new file mode 100644 index 0000000..e885c75 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/NoneStrategy.java @@ -0,0 +1,22 @@ +package me.samsuik.sakura.entity.merge.strategy; + +import me.samsuik.sakura.entity.merge.TrackedMergeHistory; +import net.minecraft.world.entity.Entity; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +final class NoneStrategy implements MergeStrategy { + static final NoneStrategy INSTANCE = new NoneStrategy(); + + @Override + public boolean trackHistory() { + return false; + } + + @Override + @Nullable + public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) { + return null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/SpawnStrategy.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/SpawnStrategy.java new file mode 100644 index 0000000..e5a43d6 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/SpawnStrategy.java @@ -0,0 +1,30 @@ +package me.samsuik.sakura.entity.merge.strategy; + +import me.samsuik.sakura.entity.merge.MergeCondition; +import me.samsuik.sakura.entity.merge.TrackedMergeHistory; +import net.minecraft.world.entity.Entity; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +final class SpawnStrategy implements MergeStrategy { + static final SpawnStrategy INSTANCE = new SpawnStrategy(); + private static final MergeCondition CONDITION = (e, shots, time) -> (shots > 16 || time >= 200); + + @Override + public boolean trackHistory() { + return true; + } + + @Override + @Nullable + public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) { + final Entity mergeInto; + if (entity.tickCount == 1 && mergeHistory.hasPreviouslyMergedAndMeetsCondition(entity, previous, CONDITION)) { + mergeInto = previous; + } else { + mergeInto = entity.compareState(previous) ? previous : null; + } + return mergeInto; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/StrictStrategy.java b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/StrictStrategy.java new file mode 100644 index 0000000..a2a956f --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/StrictStrategy.java @@ -0,0 +1,22 @@ +package me.samsuik.sakura.entity.merge.strategy; + +import me.samsuik.sakura.entity.merge.TrackedMergeHistory; +import net.minecraft.world.entity.Entity; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +final class StrictStrategy implements MergeStrategy { + static final StrictStrategy INSTANCE = new StrictStrategy(); + + @Override + public boolean trackHistory() { + return false; + } + + @Override + @Nullable + public Entity mergeEntity(Entity entity, Entity previous, TrackedMergeHistory mergeHistory) { + return entity.compareState(previous) ? previous : null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java index eb56e63..7e02bbe 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/special/TntExplosion.java @@ -44,10 +44,10 @@ public final class TntExplosion extends SpecialisedExplosion { @Override protected int getExplosionCount() { - if (this.cause.getMergeEntityData().getMergeLevel() == MergeLevel.NONE) { + if (this.cause.getMergeEntityData().mergeLevel == MergeLevel.NONE) { this.mergeEntitiesBeforeExploding(); } - return this.cause.getMergeEntityData().getCount(); + return this.cause.getMergeEntityData().count; } @Override diff --git a/sakura-server/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java b/sakura-server/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java index eddf763..1915dc9 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java @@ -6,7 +6,7 @@ import org.bukkit.inventory.ItemStack; import org.jspecify.annotations.NullMarked; @NullMarked -public class ItemStackUtil { +public final class ItemStackUtil { public static ItemStack itemWithBlankName(Material material) { return itemWithName(material, Component.empty()); }