├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitattributes ├── .editorconfig ├── sakura-server ├── src │ └── main │ │ └── java │ │ └── me │ │ └── samsuik │ │ └── sakura │ │ ├── redstone │ │ └── cache │ │ │ ├── RedstoneOriginalPower.java │ │ │ ├── RedstoneWireUpdate.java │ │ │ └── RedstoneNetworkSource.java │ │ ├── explosion │ │ ├── ExplosionToSend.java │ │ ├── density │ │ │ ├── BlockDensityCacheKey.java │ │ │ └── CachedBlockDensity.java │ │ ├── SortExplosionRays.java │ │ └── durable │ │ │ └── DurableBlockManager.java │ │ ├── player │ │ ├── gui │ │ │ ├── components │ │ │ │ ├── GuiClickEvent.java │ │ │ │ ├── GuiComponent.java │ │ │ │ ├── ItemButton.java │ │ │ │ └── ItemSwitch.java │ │ │ ├── ItemStackUtil.java │ │ │ └── FeatureGui.java │ │ ├── item │ │ │ ├── StackableBucketItem.java │ │ │ ├── MilkBucketItem.java │ │ │ ├── PowderedSnowBucketItem.java │ │ │ └── DataComponentHelper.java │ │ └── visibility │ │ │ └── PlayerVisibilitySettings.java │ │ ├── entity │ │ ├── dispensing │ │ │ └── DispenserGroup.java │ │ ├── merge │ │ │ ├── MergeCondition.java │ │ │ ├── strategy │ │ │ │ ├── NoneStrategy.java │ │ │ │ ├── StrictStrategy.java │ │ │ │ ├── SpawnStrategy.java │ │ │ │ ├── LenientStrategy.java │ │ │ │ └── MergeStrategy.java │ │ │ ├── MergeableEntity.java │ │ │ └── MergeEntityData.java │ │ └── EntityState.java │ │ ├── command │ │ ├── subcommands │ │ │ ├── FPSCommand.java │ │ │ ├── debug │ │ │ │ ├── DebugCommand.java │ │ │ │ ├── DebugLocalConfiguration.java │ │ │ │ └── DebugRedstoneCache.java │ │ │ ├── ConfigCommand.java │ │ │ └── VisualCommand.java │ │ ├── PlayerOnlySubCommand.java │ │ ├── SakuraCommand.java │ │ ├── SakuraCommands.java │ │ └── BaseSubCommand.java │ │ ├── utils │ │ ├── TickExpiry.java │ │ ├── collections │ │ │ ├── BlockPosToEntityTable.java │ │ │ └── TrackedEntityChunkMap.java │ │ └── SimpleBlockPosIterator.java │ │ ├── configuration │ │ ├── transformation │ │ │ ├── world │ │ │ │ ├── V7_FixTntDuplicationName.java │ │ │ │ ├── V11_RemovePhysicsVersion.java │ │ │ │ ├── V12_RenameUseBlockCacheAcrossExplosions.java │ │ │ │ ├── V3_RenameKnockback.java │ │ │ │ ├── V9_RenameAllowNonTntBreakingDurableBlocks.java │ │ │ │ ├── V8_RenameExplosionResistantItems.java │ │ │ │ ├── V6_FixIncorrectExtraKnockback.java │ │ │ │ ├── V2_VerticalKnockbackUseDefault.java │ │ │ │ ├── V4_RenameNonStrictMergeLevel.java │ │ │ │ ├── V10_DurableMaterialOnlyDamagedByTnt.java │ │ │ │ └── V5_CombineLoadChunksOptions.java │ │ │ └── global │ │ │ │ ├── V1_RelocateMessages.java │ │ │ │ └── V2_ConvertIconToMaterial.java │ │ ├── SakuraVersionInformation.java │ │ └── serializer │ │ │ └── MinecraftMechanicsTargetSerializer.java │ │ ├── tps │ │ ├── graph │ │ │ ├── BuiltComponentCanvas.java │ │ │ ├── ComponentCanvas.java │ │ │ ├── GraphComponents.java │ │ │ └── TPSGraph.java │ │ └── ServerTickInformation.java │ │ └── mechanics │ │ ├── LegacyBlockFormation.java │ │ └── FallingBlockBehaviour.java ├── paper-patches │ ├── files │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ ├── org │ │ │ ├── spigotmc │ │ │ │ └── SpigotConfig.java.patch │ │ │ └── bukkit │ │ │ │ └── craftbukkit │ │ │ │ ├── CraftWorld.java.patch │ │ │ │ ├── util │ │ │ │ └── Versioning.java.patch │ │ │ │ ├── entity │ │ │ │ ├── CraftFallingBlock.java.patch │ │ │ │ ├── CraftPlayer.java.patch │ │ │ │ └── CraftEntity.java.patch │ │ │ │ ├── Main.java.patch │ │ │ │ └── CraftServer.java.patch │ │ │ └── io │ │ │ └── papermc │ │ │ └── paper │ │ │ ├── ServerBuildInfoImpl.java.patch │ │ │ ├── configuration │ │ │ ├── mapping │ │ │ │ └── InnerClassFieldDiscoverer.java.patch │ │ │ ├── Configurations.java.patch │ │ │ └── PaperConfigurations.java.patch │ │ │ └── command │ │ │ └── PaperVersionCommand.java.patch │ └── features │ │ ├── 0003-Specialised-Explosions.patch │ │ └── 0001-Client-Visibility-Settings.patch └── minecraft-patches │ ├── sources │ └── net │ │ └── minecraft │ │ ├── world │ │ ├── level │ │ │ ├── redstone │ │ │ │ └── ExperimentalRedstoneUtils.java.patch │ │ │ ├── material │ │ │ │ ├── Fluid.java.patch │ │ │ │ └── LavaFluid.java.patch │ │ │ ├── block │ │ │ │ ├── entity │ │ │ │ │ └── DispenserBlockEntity.java.patch │ │ │ │ ├── IceBlock.java.patch │ │ │ │ ├── piston │ │ │ │ │ ├── PistonBaseBlock.java.patch │ │ │ │ │ └── PistonMovingBlockEntity.java.patch │ │ │ │ ├── BubbleColumnBlock.java.patch │ │ │ │ ├── SugarCaneBlock.java.patch │ │ │ │ ├── CactusBlock.java.patch │ │ │ │ ├── LiquidBlock.java.patch │ │ │ │ └── WebBlock.java.patch │ │ │ ├── chunk │ │ │ │ └── LevelChunkSection.java.patch │ │ │ └── ServerExplosion.java.patch │ │ ├── item │ │ │ ├── Item.java.patch │ │ │ ├── ItemStack.java.patch │ │ │ ├── BoatItem.java.patch │ │ │ ├── BucketItem.java.patch │ │ │ ├── EnderpearlItem.java.patch │ │ │ └── SpawnEggItem.java.patch │ │ ├── entity │ │ │ ├── Mob.java.patch │ │ │ ├── animal │ │ │ │ └── IronGolem.java.patch │ │ │ ├── Entity.java.patch │ │ │ ├── item │ │ │ │ └── ItemEntity.java.patch │ │ │ ├── projectile │ │ │ │ ├── Projectile.java.patch │ │ │ │ ├── ProjectileUtil.java.patch │ │ │ │ ├── FishingHook.java.patch │ │ │ │ ├── ThrowableProjectile.java.patch │ │ │ │ └── AbstractThrownPotion.java.patch │ │ │ └── monster │ │ │ │ └── Creeper.java.patch │ │ ├── food │ │ │ └── FoodData.java.patch │ │ └── inventory │ │ │ └── AbstractContainerMenu.java.patch │ │ ├── network │ │ └── protocol │ │ │ └── game │ │ │ └── ClientboundContainerSetSlotPacket.java.patch │ │ └── server │ │ ├── dedicated │ │ └── DedicatedServer.java.patch │ │ └── level │ │ ├── ChunkMap.java.patch │ │ ├── ServerPlayer.java.patch │ │ └── ServerLevel.java.patch │ └── features │ ├── 0016-Destroy-Waterlogged-Blocks.patch │ ├── 0029-Configure-breaking-blocks-outside-the-world-border.patch │ ├── 0023-Protect-scaffolding-from-creepers.patch │ ├── 0031-Protect-blocks-above-a-configured-Y-level-from-explo.patch │ ├── 0014-Use-maxEntityCollision-limit-for-entity-retrieval.patch │ ├── 0007-Store-Entity-Data-State.patch │ ├── 0018-Allow-explosions-to-destroy-lava.patch │ └── 0010-Optimise-explosions-in-protected-regions.patch ├── sakura-api ├── src │ └── main │ │ └── java │ │ └── me │ │ └── samsuik │ │ └── sakura │ │ ├── player │ │ └── visibility │ │ │ ├── VisibilityState.java │ │ │ ├── VisibilityTypes.java │ │ │ ├── VisibilityType.java │ │ │ └── VisibilitySettings.java │ │ ├── configuration │ │ └── local │ │ │ ├── Container.java │ │ │ └── ConfigurableKey.java │ │ ├── entity │ │ └── merge │ │ │ ├── Mergeable.java │ │ │ └── MergeLevel.java │ │ ├── redstone │ │ ├── RedstoneConfiguration.java │ │ └── RedstoneImplementation.java │ │ ├── mechanics │ │ ├── ServerType.java │ │ ├── MinecraftVersionEncoding.java │ │ └── MechanicVersion.java │ │ └── explosion │ │ └── durable │ │ └── DurableMaterial.java ├── paper-patches │ ├── files │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ └── bukkit │ │ │ ├── World.java.patch │ │ │ ├── entity │ │ │ ├── Player.java.patch │ │ │ ├── FallingBlock.java.patch │ │ │ └── Entity.java.patch │ │ │ └── Bukkit.java.patch │ └── features │ │ ├── 0001-Client-Visibility-Settings-API.patch │ │ └── 0002-Merge-Cannon-Entities-API.patch └── build.gradle.kts.patch ├── gradle.properties ├── .gitignore ├── settings.gradle.kts └── scripts ├── upstreamCommit.sh ├── upstream.sh └── apatch.sh /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samsuik/Sakura/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.sh text eol=lf 4 | gradlew text eol=lf 5 | *.bat text eol=crlf 6 | 7 | *.jar binary 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.java] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=true 5 | indent_style=space 6 | indent_size=4 7 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/redstone/cache/RedstoneOriginalPower.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.redstone.cache; 2 | 3 | public record RedstoneOriginalPower(int originalPower, int firstPower) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/explosion/ExplosionToSend.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.explosion; 2 | 3 | import net.minecraft.world.phys.Vec3; 4 | 5 | public record ExplosionToSend(Vec3 position, int blocksDestroyed) { 6 | } 7 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/player/visibility/VisibilityState.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.visibility; 2 | 3 | /** 4 | * The visibility state of a {@link VisibilityType}. 5 | */ 6 | public enum VisibilityState { 7 | ON, MODIFIED, MINIMAL, OFF; 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=me.samsuik.sakura 2 | version=1.21.10-R0.1-SNAPSHOT 3 | mcVersion=1.21.10 4 | 5 | paperRef=92ed3f00b13692d349ba09d6ab34794ca46b37fa 6 | 7 | org.gradle.jvmargs=-Xmx2G 8 | org.gradle.vfs.watch=false 9 | org.gradle.caching=true 10 | org.gradle.parallel=true 11 | # org.gradle.daemon=true 12 | # org.gradle.configureondemand=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # project files 2 | .gradle/ 3 | .idea/ 4 | build/ 5 | run/ 6 | 7 | # macos 8 | .DS_Store 9 | 10 | # upstream 11 | paper-api 12 | paper-server 13 | paper-api-generator 14 | 15 | # sakura 16 | build.gradle.kts.rej 17 | sakura-api/build.gradle.kts 18 | sakura-server/build.gradle.kts 19 | sakura-server/src/minecraft 20 | 21 | !gradle/wrapper/gradle-wrapper.jar 22 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/configuration/local/Container.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.local; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | import java.util.Map; 6 | 7 | @NullMarked 8 | public interface Container { 9 | Map contents(); 10 | 11 | Container open(); 12 | 13 | Container seal(); 14 | } 15 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/entity/merge/Mergeable.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | @NullMarked 6 | public interface Mergeable { 7 | MergeLevel getMergeLevel(); 8 | 9 | void setMergeLevel(final MergeLevel mergeLevel); 10 | 11 | int getStacked(); 12 | 13 | void setStacked(int stacked); 14 | } 15 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenLocal() 5 | maven("https://repo.papermc.io/repository/maven-public/") 6 | } 7 | } 8 | 9 | plugins { 10 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" 11 | } 12 | 13 | rootProject.name = "sakura" 14 | 15 | include("sakura-api", "sakura-server") 16 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/gui/components/GuiClickEvent.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.gui.components; 2 | 3 | import me.samsuik.sakura.player.gui.FeatureGuiInventory; 4 | import org.bukkit.event.inventory.InventoryClickEvent; 5 | import org.jspecify.annotations.NullMarked; 6 | 7 | @NullMarked 8 | public interface GuiClickEvent { 9 | void doSomething(final InventoryClickEvent event, final FeatureGuiInventory inventory); 10 | } 11 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/spigotmc/SpigotConfig.java 2 | +++ b/src/main/java/org/spigotmc/SpigotConfig.java 3 | @@ -229,7 +_,7 @@ 4 | } 5 | 6 | private static void tpsCommand() { 7 | - SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps")); 8 | + // Sakura - track tick information; replace tps command 9 | } 10 | 11 | public static int playerSample; 12 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java 2 | +++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java 3 | @@ -61,7 +_,7 @@ 4 | 5 | @Override 6 | public boolean isBrandCompatible(final @NotNull Key brandId) { 7 | - return brandId.equals(this.brandId); 8 | + return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID); // Sakura - branding changes 9 | } 10 | 11 | @Override 12 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java 2 | +++ b/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java 3 | @@ -14,6 +_,7 @@ 4 | orientation = orientation.withUp(up); 5 | } 6 | 7 | + // Sakura - redstone implementation api; conflict on change 8 | if (front != null) { 9 | orientation = orientation.withFront(front); 10 | } 11 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/gui/components/GuiComponent.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.gui.components; 2 | 3 | import me.samsuik.sakura.player.gui.FeatureGuiInventory; 4 | import org.bukkit.event.inventory.InventoryClickEvent; 5 | import org.bukkit.inventory.Inventory; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | @NullMarked 9 | public interface GuiComponent { 10 | boolean interaction(final InventoryClickEvent event, final FeatureGuiInventory featureInventory); 11 | 12 | void creation(final Inventory inventory); 13 | } 14 | -------------------------------------------------------------------------------- /sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/World.java 2 | +++ b/src/main/java/org/bukkit/World.java 3 | @@ -4559,6 +_,11 @@ 4 | void setSendViewDistance(int viewDistance); 5 | // Paper end - view distance api 6 | 7 | + // Sakura start - local config api 8 | + @NotNull 9 | + me.samsuik.sakura.configuration.local.LocalConfigurationAccessor localConfig(); 10 | + // Sakura end - local config api 11 | + 12 | /** 13 | * Gets all generated structures that intersect the chunk at the given 14 | * coordinates.
15 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/item/Item.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/item/Item.java 2 | +++ b/net/minecraft/world/item/Item.java 3 | @@ -134,6 +_,11 @@ 4 | } 5 | } 6 | 7 | + // Sakura start - modify components sent to the client 8 | + public void modifyComponentsSentToClient(final net.minecraft.core.component.PatchedDataComponentMap components) { 9 | + } 10 | + // Sakura end - modify components sent to the client 11 | + 12 | @Deprecated 13 | public Holder.Reference builtInRegistryHolder() { 14 | return this.builtInRegistryHolder; 15 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java 2 | +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java 3 | @@ -296,6 +_,12 @@ 4 | ).isValid(); 5 | } 6 | // Paper end 7 | + // Sakura start - local config api 8 | + @Override 9 | + public final me.samsuik.sakura.configuration.local.LocalConfigurationAccessor localConfig() { 10 | + return this.getHandle().localConfig(); 11 | + } 12 | + // Sakura end - local config api 13 | 14 | private static final Random rand = new Random(); 15 | 16 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/Mob.java 2 | +++ b/net/minecraft/world/entity/Mob.java 3 | @@ -757,7 +_,7 @@ 4 | protected final void serverAiStep() { 5 | this.noActionTime++; 6 | // Paper start - Allow nerfed mobs to jump and float 7 | - if (!this.aware) { 8 | + if (!this.aware || this.level().sakuraConfig().entity.disableMobAi) { // Sakura - add option to disable entity ai 9 | if (this.goalFloat != null) { 10 | if (this.goalFloat.canUse()) this.goalFloat.tick(); 11 | this.getJumpControl().tick(); 12 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/network/protocol/game/ClientboundContainerSetSlotPacket.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/network/protocol/game/ClientboundContainerSetSlotPacket.java 2 | +++ b/net/minecraft/network/protocol/game/ClientboundContainerSetSlotPacket.java 3 | @@ -19,7 +_,7 @@ 4 | this.containerId = containerId; 5 | this.stateId = stateId; 6 | this.slot = slot; 7 | - this.itemStack = itemStack.copy(); 8 | + this.itemStack = itemStack.copyForPacket(); // Sakura - modify components sent to the client 9 | } 10 | 11 | private ClientboundContainerSetSlotPacket(RegistryFriendlyByteBuf buffer) { 12 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.redstone; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | /** 6 | * Configuration for redstone behavior 7 | * 8 | * @param implementation the redstone implementation to use 9 | * @param cache whether to cache redstone calculations 10 | */ 11 | @NullMarked 12 | public record RedstoneConfiguration(RedstoneImplementation implementation, boolean cache) { 13 | public static RedstoneConfiguration withImplementation(final RedstoneImplementation implementation) { 14 | return new RedstoneConfiguration(implementation, false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.redstone; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | /** 6 | * The redstone implementation to use. 7 | */ 8 | @NullMarked 9 | public enum RedstoneImplementation { 10 | VANILLA("vanilla"), 11 | EIGENCRAFT("eigencraft"), 12 | ALTERNATE_CURRENT("alternate-current"); 13 | 14 | private final String friendlyName; 15 | 16 | RedstoneImplementation(final String friendlyName) { 17 | this.friendlyName = friendlyName; 18 | } 19 | 20 | public final String getFriendlyName() { 21 | return this.friendlyName; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/dispensing/DispenserGroup.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.dispensing; 2 | 3 | import it.unimi.dsi.fastutil.longs.Long2ObjectMap; 4 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.world.level.ChunkPos; 7 | 8 | public final class DispenserGroup { 9 | private final Long2ObjectMap columns = new Long2ObjectOpenHashMap<>(); 10 | 11 | public BlockPos getSpawnPosition(final BlockPos pos) { 12 | final long column = ChunkPos.asLong(pos.getX(), pos.getZ()); 13 | return this.columns.computeIfAbsent(column, c -> pos); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeCondition.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge; 2 | 3 | import net.minecraft.world.entity.Entity; 4 | import org.jspecify.annotations.NullMarked; 5 | 6 | @NullMarked 7 | public interface MergeCondition { 8 | default MergeCondition and(final MergeCondition condition) { 9 | return (e,c,t) -> this.accept(e,c,t) && condition.accept(e,c,t); 10 | } 11 | 12 | default MergeCondition or(final MergeCondition condition) { 13 | return (e,c,t) -> this.accept(e,c,t) || condition.accept(e,c,t); 14 | } 15 | 16 | boolean accept(final Entity entity, final int attempts, final long sinceCreation); 17 | } 18 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/animal/IronGolem.java 2 | +++ b/net/minecraft/world/entity/animal/IronGolem.java 3 | @@ -231,6 +_,13 @@ 4 | } 5 | } 6 | 7 | + // Sakura start - configure iron golems taking fall damage 8 | + @Override 9 | + protected final boolean isFallDamageImmune() { 10 | + return !this.level().sakuraConfig().entity.ironGolemsTakeFalldamage && super.isFallDamageImmune(); 11 | + } 12 | + // Sakura end - configure iron golems taking fall damage 13 | + 14 | public int getAttackAnimationTick() { 15 | return this.attackAnimationTick; 16 | } 17 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/Entity.java 2 | +++ b/net/minecraft/world/entity/Entity.java 3 | @@ -533,6 +_,7 @@ 4 | } 5 | } 6 | // Paper end - optimise entity tracker 7 | + public boolean pushedByFluid = true; // Sakura - entity pushed by fluid api 8 | 9 | public Entity(EntityType type, Level level) { 10 | this.type = type; 11 | @@ -4272,7 +_,7 @@ 12 | } 13 | 14 | public boolean isPushedByFluid() { 15 | - return true; 16 | + return this.pushedByFluid; // Sakura - entity pushed by fluid api 17 | } 18 | 19 | public static double getViewScale() { 20 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command.subcommands; 2 | 3 | import me.samsuik.sakura.command.PlayerOnlySubCommand; 4 | import me.samsuik.sakura.player.visibility.VisibilityGui; 5 | import org.bukkit.entity.Player; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | @NullMarked 9 | public final class FPSCommand extends PlayerOnlySubCommand { 10 | private final VisibilityGui visibilityGui = new VisibilityGui(); 11 | 12 | public FPSCommand(final String name) { 13 | super(name); 14 | } 15 | 16 | @Override 17 | public void execute(final Player player, final String[] args) { 18 | this.visibilityGui.showTo(player); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/item/ItemStack.java 2 | +++ b/net/minecraft/world/item/ItemStack.java 3 | @@ -823,6 +_,15 @@ 4 | } 5 | } 6 | 7 | + // Sakura start - modify components sent to the client 8 | + public ItemStack copyForPacket() { 9 | + final ItemStack stackCopy = this.copy(); 10 | + final Item item = stackCopy.getItem(); 11 | + item.modifyComponentsSentToClient(stackCopy.components); 12 | + return stackCopy; 13 | + } 14 | + // Sakura end - modify components sent to the client 15 | + 16 | public ItemStack copyWithCount(int count) { 17 | if (this.isEmpty()) { 18 | return EMPTY; 19 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/Fluid.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/material/Fluid.java 2 | +++ b/net/minecraft/world/level/material/Fluid.java 3 | @@ -73,6 +_,12 @@ 4 | 5 | protected abstract boolean canBeReplacedWith(FluidState state, BlockGetter level, BlockPos pos, Fluid fluid, Direction direction); 6 | 7 | + // Sakura start - lava flow speed api 8 | + public int getTickDelay(final Level world, final BlockPos pos) { 9 | + return this.getTickDelay(world); 10 | + } 11 | + // Sakura end - lava flow speed api 12 | + 13 | protected abstract Vec3 getFlow(BlockGetter level, BlockPos pos, FluidState fluidState); 14 | 15 | public abstract int getTickDelay(LevelReader level); 16 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java 2 | +++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java 3 | @@ -11,7 +_,7 @@ 4 | public static String getBukkitVersion() { 5 | String result = "Unknown-Version"; 6 | 7 | - InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/io.papermc.paper/paper-api/pom.properties"); 8 | + InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/me.samsuik.sakura/sakura-api/pom.properties"); // Sakura - branding changes 9 | Properties properties = new Properties(); 10 | 11 | if (stream != null) { 12 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/gui/ItemStackUtil.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.gui; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import org.bukkit.Material; 5 | import org.bukkit.inventory.ItemStack; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | @NullMarked 9 | public final class ItemStackUtil { 10 | public static ItemStack itemWithBlankName(final Material material) { 11 | return itemWithName(material, Component.empty()); 12 | } 13 | 14 | public static ItemStack itemWithName(final Material material, final Component component) { 15 | final ItemStack item = new ItemStack(material); 16 | item.editMeta(meta -> meta.itemName(component)); 17 | return item; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/item/BoatItem.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/item/BoatItem.java 2 | +++ b/net/minecraft/world/item/BoatItem.java 3 | @@ -63,7 +_,7 @@ 4 | return InteractionResult.FAIL; 5 | } else { 6 | boat.setYRot(player.getYRot()); 7 | - if (!level.noCollision(boat, boat.getBoundingBox())) { 8 | + if (!level.noCollision(boat, boat.getBoundingBox()) || !level.getWorldBorder().isWithinBounds(boat.getBoundingBox())) { // Sakura - fix placing boats outside the world border 9 | return InteractionResult.FAIL; 10 | } else { 11 | if (!level.isClientSide()) { 12 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/NoneStrategy.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge.strategy; 2 | 3 | import me.samsuik.sakura.entity.merge.TrackedMergeHistory; 4 | import net.minecraft.world.entity.Entity; 5 | import org.jspecify.annotations.NullMarked; 6 | import org.jspecify.annotations.Nullable; 7 | 8 | @NullMarked 9 | final class NoneStrategy implements MergeStrategy { 10 | static final NoneStrategy INSTANCE = new NoneStrategy(); 11 | 12 | @Override 13 | public boolean trackHistory() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) { 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugCommand.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command.subcommands.debug; 2 | 3 | import me.samsuik.sakura.command.BaseMenuCommand; 4 | import me.samsuik.sakura.command.SakuraCommands; 5 | import org.bukkit.command.Command; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | @NullMarked 9 | public final class DebugCommand extends BaseMenuCommand { 10 | public DebugCommand(final String name) { 11 | super(name); 12 | } 13 | 14 | @Override 15 | public String header() { 16 | return "Command for debugging Sakura features and api"; 17 | } 18 | 19 | @Override 20 | public Iterable subCommands() { 21 | return SakuraCommands.DEBUG_COMMANDS; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/mechanics/ServerType.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.mechanics; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | /** 6 | * Types of server software that have different cannon mechanics. 7 | */ 8 | @NullMarked 9 | public final class ServerType { 10 | public static final byte VANILLA = 0; 11 | public static final byte SPIGOT = 1; 12 | public static final byte PAPER = 2; 13 | public static final byte SAKE = 32; 14 | 15 | public static String name(final byte serverType) { 16 | return switch (serverType) { 17 | case 0 -> "vanilla"; 18 | case 1 -> "spigot"; 19 | case 2 -> "paper"; 20 | case 32 -> "sake"; 21 | default -> "unknown"; 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/utils/TickExpiry.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.utils; 2 | 3 | import com.google.common.base.Preconditions; 4 | import org.jspecify.annotations.NullMarked; 5 | 6 | @NullMarked 7 | public final class TickExpiry { 8 | private long tick; 9 | private final int expiration; 10 | 11 | public TickExpiry(final long tick, final int expiration) { 12 | Preconditions.checkArgument(expiration > 0, "Expiration cannot be lower or equal to 0"); 13 | this.tick = tick; 14 | this.expiration = expiration; 15 | } 16 | 17 | public void refresh(final long tick) { 18 | this.tick = tick; 19 | } 20 | 21 | public boolean isExpired(final long tick) { 22 | return this.tick < tick - this.expiration; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Player.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/entity/Player.java 2 | +++ b/src/main/java/org/bukkit/entity/Player.java 3 | @@ -66,6 +_,12 @@ 4 | @NullMarked 5 | public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified, net.kyori.adventure.bossbar.BossBarViewer, com.destroystokyo.paper.network.NetworkClient { // Paper 6 | 7 | + // Sakura start - entity tracking range modifier 8 | + double getTrackingRangeModifier(); 9 | + 10 | + void setTrackingRangeModifier(final double modifier); 11 | + // Sakura end - entity tracking range modifier 12 | + 13 | // Paper start 14 | @Override 15 | default net.kyori.adventure.identity.Identity identity() { 16 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/food/FoodData.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/food/FoodData.java 2 | +++ b/net/minecraft/world/food/FoodData.java 3 | @@ -66,7 +_,7 @@ 4 | } 5 | 6 | boolean _boolean = serverLevel.getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION); 7 | - if (_boolean && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) { 8 | + if (_boolean && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20 && player.level().sakuraConfig().players.combat.fastHealthRegen) { // Sakura - configure fast health regen 9 | this.tickTimer++; 10 | if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit 11 | float min = Math.min(this.saturationLevel, 6.0F); 12 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeableEntity.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | @NullMarked 6 | public interface MergeableEntity { 7 | MergeEntityData getMergeEntityData(); 8 | 9 | boolean isSafeToMergeInto(final MergeableEntity entity, final boolean ticksLived); 10 | 11 | default boolean tryToRespawnEntity() { 12 | final MergeEntityData mergeData = this.getMergeEntityData(); 13 | final int originalCount = mergeData.count; 14 | if (originalCount > 1) { 15 | mergeData.count = 0; 16 | this.respawnEntity(originalCount); 17 | return true; 18 | } 19 | return false; 20 | } 21 | 22 | void respawnEntity(final int count); 23 | } 24 | -------------------------------------------------------------------------------- /sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/FallingBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/entity/FallingBlock.java 2 | +++ b/src/main/java/org/bukkit/entity/FallingBlock.java 3 | @@ -9,6 +_,22 @@ 4 | */ 5 | public interface FallingBlock extends Entity { 6 | 7 | + // Sakura start - falling block height parity api 8 | + /** 9 | + * Gets if falling block has height parity 10 | + * 11 | + * @return parity 12 | + */ 13 | + boolean getHeightParity(); 14 | + 15 | + /** 16 | + * Sets falling block height parity 17 | + * 18 | + * @param parity value 19 | + */ 20 | + void setHeightParity(final boolean parity); 21 | + // Sakura end - falling block height parity api 22 | + 23 | /** 24 | * Get the Material of the falling block 25 | * 26 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/StrictStrategy.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge.strategy; 2 | 3 | import me.samsuik.sakura.entity.merge.TrackedMergeHistory; 4 | import net.minecraft.world.entity.Entity; 5 | import org.jspecify.annotations.NullMarked; 6 | import org.jspecify.annotations.Nullable; 7 | 8 | @NullMarked 9 | final class StrictStrategy implements MergeStrategy { 10 | static final StrictStrategy INSTANCE = new StrictStrategy(); 11 | 12 | @Override 13 | public boolean trackHistory() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) { 19 | return entity.compareState(previous) ? previous : null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/PlayerOnlySubCommand.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.bukkit.entity.Player; 5 | import org.jspecify.annotations.NullMarked; 6 | 7 | @NullMarked 8 | public abstract class PlayerOnlySubCommand extends BaseSubCommand { 9 | public PlayerOnlySubCommand(final String name) { 10 | super(name); 11 | } 12 | 13 | public abstract void execute(final Player player, final String[] args); 14 | 15 | public final void execute(final CommandSender sender, final String[] args) { 16 | if (sender instanceof Player player) { 17 | this.execute(player, args); 18 | } else { 19 | sender.sendRichMessage("This command can only be used by players"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java 2 | +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java 3 | @@ -14,6 +_,18 @@ 4 | super(server, entity); 5 | } 6 | 7 | + // Sakura start - falling block height parity api 8 | + @Override 9 | + public final void setHeightParity(final boolean heightParity) { 10 | + this.getHandle().heightParity = heightParity; 11 | + } 12 | + 13 | + @Override 14 | + public final boolean getHeightParity() { 15 | + return this.getHandle().heightParity; 16 | + } 17 | + // Sakura end - falling block height parity api 18 | + 19 | @Override 20 | public FallingBlockEntity getHandle() { 21 | return (FallingBlockEntity) this.entity; 22 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/item/BucketItem.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/item/BucketItem.java 2 | +++ b/net/minecraft/world/item/BucketItem.java 3 | @@ -147,7 +_,7 @@ 4 | // CraftBukkit end 5 | if (!flag2) { 6 | return hitResult != null && this.emptyContents(entity, level, hitResult.getBlockPos().relative(hitResult.getDirection()), null, direction, clicked, itemstack, hand); // CraftBukkit 7 | - } else if (level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { 8 | + } else if (!level.sakuraConfig().environment.allowWaterInTheNether && level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { // Sakura - allow water in the nether 9 | int x = pos.getX(); 10 | int y = pos.getY(); 11 | int z = pos.getZ(); 12 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java 2 | +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java 3 | @@ -251,6 +_,18 @@ 4 | this.firstPlayed = System.currentTimeMillis(); 5 | } 6 | 7 | + // Sakura start - entity tracking range modifier 8 | + @Override 9 | + public final double getTrackingRangeModifier() { 10 | + return this.getHandle().trackingRangeModifier * 100.0; 11 | + } 12 | + 13 | + @Override 14 | + public final void setTrackingRangeModifier(final double modifier) { 15 | + this.getHandle().trackingRangeModifier = modifier / 100.0; 16 | + } 17 | + // Sakura end - entity tracking range modifier 18 | + 19 | @Override 20 | public ServerPlayer getHandle() { 21 | return (ServerPlayer) this.entity; 22 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java 2 | +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java 3 | @@ -113,6 +_,18 @@ 4 | this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); 5 | } 6 | 7 | + // Sakura start - entity pushed by fluid api 8 | + @Override 9 | + public final boolean isPushedByFluid() { 10 | + return this.getHandle().isPushedByFluid(); 11 | + } 12 | + 13 | + @Override 14 | + public final void setPushedByFluid(final boolean push) { 15 | + this.getHandle().pushedByFluid = push; 16 | + } 17 | + // Sakura end - entity pushed by fluid api 18 | + 19 | public static CraftEntity getEntity(CraftServer server, T entity) { 20 | Preconditions.checkArgument(entity != null, "Unknown entity"); 21 | 22 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/item/ItemEntity.java 2 | +++ b/net/minecraft/world/entity/item/ItemEntity.java 3 | @@ -359,6 +_,13 @@ 4 | 5 | @Override 6 | public boolean ignoreExplosion(Explosion explosion) { 7 | + // Sakura start - add list of items that ignore explosions 8 | + final java.util.Set blastResistantTypes = this.level().sakuraConfig().entity.items.blastResistant.items; 9 | + final boolean whitelist = this.level().sakuraConfig().entity.items.blastResistant.whitelistOverBlacklist; 10 | + if (blastResistantTypes.contains(this.getItem().getItem()) != whitelist) { 11 | + return true; 12 | + } 13 | + // Sakura end - add list of items that ignore explosions 14 | return !explosion.shouldAffectBlocklikeEntities() || super.ignoreExplosion(explosion); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /scripts/upstreamCommit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # requires curl & jq 4 | 5 | # upstreamCommit 6 | # param: bashHash - the commit hash to use for comparing commits (baseHash...newHash) 7 | # param: newHash - the commit hash to use for comparing commits 8 | 9 | ( 10 | set -e 11 | PS1="$" 12 | 13 | paper=$(curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/PaperMC/Paper/compare/$1...$2 | jq -r '.commits[] | "PaperMC/Paper@\(.sha[:7]) \(.commit.message | split("\r\n")[0] | split("\n")[0])"') 14 | 15 | updated="" 16 | logsuffix="" 17 | if [ ! -z "paper" ]; then 18 | logsuffix="$logsuffix\n\nPaper Changes:\n$paper" 19 | updated="Paper" 20 | fi 21 | disclaimer="Upstream has released updates that appear to apply and compile correctly" 22 | 23 | log="${UP_LOG_PREFIX}Updated Upstream ($updated)\n\n${disclaimer}${logsuffix}" 24 | 25 | echo -e "$log" | git commit -F - 26 | 27 | ) || exit 1 28 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java 2 | +++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java 3 | @@ -71,8 +_,11 @@ 4 | int i = -1; 5 | int i1 = 1; 6 | 7 | + // Sakura start - configure random item dispensing 8 | + final boolean randomItemSelection = this.level.sakuraConfig().technical.dispenserRandomItemSelection || this instanceof DropperBlockEntity; 9 | for (int i2 = 0; i2 < this.items.size(); i2++) { 10 | - if (!this.items.get(i2).isEmpty() && random.nextInt(i1++) == 0) { 11 | + if (!this.items.get(i2).isEmpty() && (!randomItemSelection && i == -1 || randomItemSelection && random.nextInt(i1++) == 0)) { 12 | + // Sakura end - configure random item dispensing 13 | i = i2; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/Main.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/craftbukkit/Main.java 2 | +++ b/src/main/java/org/bukkit/craftbukkit/Main.java 3 | @@ -164,6 +_,14 @@ 4 | .defaultsTo(new File[] {}) 5 | .describedAs("Jar file"); 6 | 7 | + // Sakura start - sakura configuration files 8 | + acceptsAll(asList("sakura-dir", "sakura-settings-directory"), "Directory for Sakura settings") 9 | + .withRequiredArg() 10 | + .ofType(File.class) 11 | + .defaultsTo(new File(me.samsuik.sakura.configuration.SakuraConfigurations.CONFIG_DIR)) 12 | + .describedAs("Config directory"); 13 | + // Sakura end - sakura configuration files 14 | + 15 | this.accepts("server-name", "Name of the server") 16 | .withRequiredArg() 17 | .ofType(String.class) 18 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V7_FixTntDuplicationName.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 4 | import org.spongepowered.configurate.NodePath; 5 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 6 | 7 | import static org.spongepowered.configurate.NodePath.*; 8 | import static org.spongepowered.configurate.transformation.TransformAction.rename; 9 | 10 | public final class V7_FixTntDuplicationName { 11 | private static final int VERSION = 7; 12 | private static final NodePath OLD_PATH = path("technical", "allow-t-n-t-duplication"); 13 | private static final String NEW_NAME = "allow-tnt-duplication"; 14 | 15 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 16 | builder.addVersion(VERSION, ConfigurationTransformations.transform(OLD_PATH, rename(NEW_NAME))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V11_RemovePhysicsVersion.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 4 | import org.jspecify.annotations.NullMarked; 5 | import org.spongepowered.configurate.NodePath; 6 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 7 | import org.spongepowered.configurate.transformation.TransformAction; 8 | 9 | import static org.spongepowered.configurate.NodePath.path; 10 | 11 | @NullMarked 12 | public final class V11_RemovePhysicsVersion { 13 | private static final int VERSION = 11; 14 | private static final NodePath PHYSICS_VERSION_PATH = path("cannons", "mechanics", "physics-version"); 15 | 16 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 17 | builder.addVersion(VERSION, ConfigurationTransformations.transform(PHYSICS_VERSION_PATH, TransformAction.remove())); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/explosion/density/BlockDensityCacheKey.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.explosion.density; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.util.Mth; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.phys.Vec3; 7 | import org.jspecify.annotations.NullMarked; 8 | 9 | @NullMarked 10 | public record BlockDensityCacheKey(Vec3 explosionPos, Vec3 entityPos) { 11 | public BlockDensityCacheKey(final Vec3 explosionPos, final Entity entity) { 12 | this(explosionPos, entity.position()); 13 | } 14 | 15 | public static int getLenientKey(final Vec3 explosionPos, final BlockPos entityBlockPos) { 16 | int key = Mth.floor(explosionPos.x()); 17 | key = 31 * key + Mth.floor(explosionPos.y()); 18 | key = 31 * key + Mth.floor(explosionPos.z()); 19 | key = 31 * key + entityBlockPos.getX(); 20 | key = 31 * key + entityBlockPos.getY(); 21 | key = 31 * key + entityBlockPos.getZ(); 22 | return key; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sakura-api/paper-patches/files/src/main/java/org/bukkit/Bukkit.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/Bukkit.java 2 | +++ b/src/main/java/org/bukkit/Bukkit.java 3 | @@ -127,6 +_,20 @@ 4 | // Paper end 5 | } 6 | 7 | + // Sakura start - customise version command; expose git information 8 | + @NotNull 9 | + public static String getGitInformation() { 10 | + final io.papermc.paper.ServerBuildInfo version = io.papermc.paper.ServerBuildInfo.buildInfo(); 11 | + final String gitBranch = version.gitBranch().orElse("Dev"); 12 | + final String gitCommit = version.gitCommit().orElse(""); 13 | + String branchMsg = " on " + gitBranch; 14 | + if ("master".equals(gitBranch) || "main".equals(gitBranch)) { 15 | + branchMsg = ""; // Don't show branch on main/master 16 | + } 17 | + return "(Git: " + gitCommit + branchMsg + ")"; 18 | + } 19 | + // Sakura end - customise version command; expose git information 20 | + 21 | /** 22 | * Gets the name of this server implementation. 23 | * 24 | -------------------------------------------------------------------------------- /sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Entity.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/entity/Entity.java 2 | +++ b/src/main/java/org/bukkit/entity/Entity.java 3 | @@ -39,6 +_,22 @@ 4 | */ 5 | public interface Entity extends Metadatable, CommandSender, Nameable, PersistentDataHolder, net.kyori.adventure.text.event.HoverEventSource, net.kyori.adventure.sound.Sound.Emitter, DataComponentView { // Paper 6 | 7 | + // Sakura start - entity pushed by fluid api 8 | + /** 9 | + * Gets if the entity will be pushed by fluid. 10 | + * 11 | + * @return if this entity can be pushed by fluid. 12 | + */ 13 | + boolean isPushedByFluid(); 14 | + 15 | + /** 16 | + * Sets if the entity will be pushed by fluid. 17 | + * 18 | + * @param state whether entity should be pushed by fluid 19 | + */ 20 | + void setPushedByFluid(final boolean state); 21 | + // Sakura end - entity pushed by fluid api 22 | + 23 | /** 24 | * Gets the entity's current position 25 | * 26 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/projectile/Projectile.java 2 | +++ b/net/minecraft/world/entity/projectile/Projectile.java 3 | @@ -51,6 +_,12 @@ 4 | super(type, level); 5 | } 6 | 7 | + // Sakura start - configure potion mechanics 8 | + protected boolean breakInsideOwner() { 9 | + return false; 10 | + } 11 | + // Sakura end - configure potion mechanics 12 | + 13 | protected void setOwner(@Nullable EntityReference owner) { 14 | this.owner = owner; 15 | this.refreshProjectileSource(false); // Paper 16 | @@ -401,7 +_,7 @@ 17 | } 18 | } 19 | // Paper end - Cancel hit for vanished entities 20 | - return owner == null || this.leftOwner || !owner.isPassengerOfSameVehicle(target); 21 | + return owner == null || this.leftOwner || this.breakInsideOwner() || !owner.isPassengerOfSameVehicle(target); // Sakura - configure potion mechanics 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/server/dedicated/DedicatedServer.java 2 | +++ b/net/minecraft/server/dedicated/DedicatedServer.java 3 | @@ -277,6 +_,11 @@ 4 | this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark 5 | com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics 6 | com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now 7 | + // Sakura start - sakura configuration files 8 | + sakuraConfigurations.initializeGlobalConfiguration(this.registryAccess()); 9 | + sakuraConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); 10 | + me.samsuik.sakura.command.SakuraCommands.registerCommands(this); 11 | + // Sakura end - sakura configuration files 12 | 13 | // this.worldData.setGameType(properties.gameMode.get()); // CraftBukkit - moved to world loading 14 | LOGGER.info("Default game type: {}", properties.gameMode.get()); 15 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V12_RenameUseBlockCacheAcrossExplosions.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 4 | import org.spongepowered.configurate.NodePath; 5 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 6 | 7 | import static org.spongepowered.configurate.transformation.TransformAction.rename; 8 | 9 | public final class V12_RenameUseBlockCacheAcrossExplosions { 10 | private static final int VERSION = 12; 11 | private static final NodePath USE_BLOCK_CACHE_ACROSS_EXPLOSIONS_PATH = NodePath.path("cannons", "explosion", "use-block-cache-across-explosions"); 12 | private static final String NEW_NAME = "reuse-block-cache-across-explosions"; 13 | 14 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 15 | builder.addVersion(VERSION, ConfigurationTransformations.transform(USE_BLOCK_CACHE_ACROSS_EXPLOSIONS_PATH, rename(NEW_NAME))); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java 2 | +++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java 3 | @@ -81,7 +_,7 @@ 4 | } 5 | 6 | @SuppressWarnings("unchecked") 7 | - private static InnerClassFieldDiscoverer create(final Map, Object> overrides, final List>> fieldProcessors) { 8 | + public static InnerClassFieldDiscoverer create(final Map, Object> overrides, final List>> fieldProcessors) { // Sakura - sakura configuration files; private -> public 9 | final Map, List>>> processors = new LinkedHashMap<>(); 10 | for (final Definition> definition : fieldProcessors) { 11 | processors.computeIfAbsent(definition.annotation(), k -> new ArrayList<>()).add(definition); 12 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/IceBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/IceBlock.java 2 | +++ b/net/minecraft/world/level/block/IceBlock.java 3 | @@ -40,7 +_,7 @@ 4 | public void afterDestroy(Level level, BlockPos pos, ItemStack stack) { 5 | // Paper end - Improve Block#breakNaturally API 6 | if (!EnchantmentHelper.hasTag(stack, EnchantmentTags.PREVENTS_ICE_MELTING)) { 7 | - if (level.dimensionType().ultraWarm()) { 8 | + if (!level.sakuraConfig().environment.allowWaterInTheNether && level.dimensionType().ultraWarm()) { // Sakura - allow water in the nether 9 | level.removeBlock(pos, false); 10 | return; 11 | } 12 | @@ -65,7 +_,7 @@ 13 | return; 14 | } 15 | // CraftBukkit end 16 | - if (level.dimensionType().ultraWarm()) { 17 | + if (!level.sakuraConfig().environment.allowWaterInTheNether && level.dimensionType().ultraWarm()) { // Sakura - allow water in the nether 18 | level.removeBlock(pos, false); 19 | } else { 20 | level.setBlockAndUpdate(pos, meltsInto()); 21 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command; 2 | 3 | import com.google.common.collect.Iterables; 4 | import net.minecraft.server.MinecraftServer; 5 | import org.bukkit.command.Command; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | import java.util.*; 9 | 10 | @NullMarked 11 | public final class SakuraCommand extends BaseMenuCommand { 12 | public SakuraCommand(final String name) { 13 | super(name); 14 | this.description = ""; 15 | } 16 | 17 | @Override 18 | public String header() { 19 | return "This is the main command for Sakura.\n" + 20 | "All exclusive commands are listed below."; 21 | } 22 | 23 | @Override 24 | public Iterable helpCommands() { 25 | return SakuraCommands.COMMANDS.values(); 26 | } 27 | 28 | @Override 29 | public Iterable subCommands() { 30 | final Command versionCommand = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); 31 | return Iterables.concat(SakuraCommands.SUB_COMMANDS, List.of(versionCommand)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/tps/graph/BuiltComponentCanvas.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.tps.graph; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.kyori.adventure.text.Component; 5 | import org.jspecify.annotations.NullMarked; 6 | 7 | import java.util.List; 8 | 9 | @NullMarked 10 | public final class BuiltComponentCanvas { 11 | private final List components; 12 | 13 | BuiltComponentCanvas(final List components) { 14 | this.components = components; 15 | } 16 | 17 | public void appendLeft(final Component component) { 18 | this.components.replaceAll(component::append); 19 | } 20 | 21 | public void appendRight(final Component component) { 22 | this.components.replaceAll(row -> row.append(component)); 23 | } 24 | 25 | public void header(final Component component) { 26 | this.components.addFirst(component); 27 | } 28 | 29 | public void footer(final Component component) { 30 | this.components.add(component); 31 | } 32 | 33 | public ImmutableList components() { 34 | return ImmutableList.copyOf(this.components); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/item/StackableBucketItem.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.item; 2 | 3 | import net.minecraft.core.component.DataComponentMap; 4 | import net.minecraft.core.component.DataComponents; 5 | import net.minecraft.core.component.PatchedDataComponentMap; 6 | import net.minecraft.world.item.BucketItem; 7 | import net.minecraft.world.level.material.Fluid; 8 | import org.jspecify.annotations.NullMarked; 9 | 10 | @NullMarked 11 | public final class StackableBucketItem extends BucketItem { 12 | public StackableBucketItem(final Fluid content, final Properties properties) { 13 | super(content, properties); 14 | } 15 | 16 | @Override 17 | public DataComponentMap components() { 18 | return DataComponentHelper.updateBucketMaxStackSize(super.components()); 19 | } 20 | 21 | @Override 22 | public void modifyComponentsSentToClient(final PatchedDataComponentMap components) { 23 | final int maxStackSize = DataComponentHelper.bucketMaxStackSize(); 24 | if (maxStackSize > 1 && maxStackSize <= 99) { 25 | components.set(DataComponents.MAX_STACK_SIZE, maxStackSize); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sakura-api/paper-patches/features/0001-Client-Visibility-Settings-API.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Tue, 21 Sep 2021 23:54:25 +0100 4 | Subject: [PATCH] Client Visibility Settings API 5 | 6 | 7 | diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java 8 | index bf8a7c425ebc8cf1546b5f891092ec317b298f3c..756336415e4e18b7871556bf1fe4ec7690ea4047 100644 9 | --- a/src/main/java/org/bukkit/entity/Player.java 10 | +++ b/src/main/java/org/bukkit/entity/Player.java 11 | @@ -71,6 +71,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM 12 | 13 | void setTrackingRangeModifier(final double modifier); 14 | // Sakura end - entity tracking range modifier 15 | + // Sakura start - client visibility settings api 16 | + /** 17 | + * Server-side api to disable sending visual effects to the client. 18 | + * 19 | + * @return visibility api 20 | + */ 21 | + me.samsuik.sakura.player.visibility.VisibilitySettings getVisibility(); 22 | + // Sakura end - client visibility settings api 23 | 24 | // Paper start 25 | @Override 26 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/player/visibility/VisibilityTypes.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.visibility; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.jspecify.annotations.NullMarked; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @NullMarked 10 | public final class VisibilityTypes { 11 | private static final List TYPES = new ArrayList<>(); 12 | 13 | public static final VisibilityType TNT = create("tnt", true); 14 | public static final VisibilityType SAND = create("sand", true); 15 | public static final VisibilityType EXPLOSIONS = create("explosions", true); 16 | public static final VisibilityType SPAWNERS = create("spawners", false); 17 | public static final VisibilityType PISTONS = create("pistons", false); 18 | 19 | public static ImmutableList types() { 20 | return ImmutableList.copyOf(TYPES); 21 | } 22 | 23 | private static VisibilityType create(final String key, final boolean minimal) { 24 | final VisibilityType newVisibilityType = VisibilityType.from(key, minimal); 25 | TYPES.add(newVisibilityType); 26 | return newVisibilityType; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/SpawnStrategy.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge.strategy; 2 | 3 | import me.samsuik.sakura.entity.merge.MergeCondition; 4 | import me.samsuik.sakura.entity.merge.TrackedMergeHistory; 5 | import net.minecraft.world.entity.Entity; 6 | import org.jspecify.annotations.NullMarked; 7 | import org.jspecify.annotations.Nullable; 8 | 9 | @NullMarked 10 | final class SpawnStrategy implements MergeStrategy { 11 | static final SpawnStrategy INSTANCE = new SpawnStrategy(); 12 | private static final MergeCondition CONDITION = (e, shots, time) -> (shots > 16 || time >= 200); 13 | 14 | @Override 15 | public boolean trackHistory() { 16 | return true; 17 | } 18 | 19 | @Override 20 | public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) { 21 | final Entity mergeInto; 22 | if (entity.tickCount == 1 && mergeHistory.hasPreviouslyMergedAndMeetsCondition(entity, previous, CONDITION)) { 23 | mergeInto = previous; 24 | } else { 25 | mergeInto = entity.compareState(previous) ? previous : null; 26 | } 27 | return mergeInto; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java 2 | +++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java 3 | @@ -370,6 +_,14 @@ 4 | for (int i1 = toPush.size() - 1; i1 >= 0; i1--) { 5 | // Paper start - fix a variety of piston desync dupes 6 | boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; 7 | + // Sakura start - configure tnt duplication 8 | + if (level.sakuraConfig().technical.allowTntDuplication) { 9 | + final BlockState movedState = list.get(i1); 10 | + if (movedState.is(Blocks.TNT) || movedState.getBlock() instanceof net.minecraft.world.level.block.BaseCoralPlantTypeBlock) { 11 | + allowDesync = true; 12 | + } 13 | + } 14 | + // Sakura end - configure tnt duplication 15 | BlockPos blockPos2; 16 | BlockPos oldPos = blockPos2 = toPush.get(i1); 17 | BlockState blockState1 = allowDesync ? level.getBlockState(oldPos) : null; 18 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ProjectileUtil.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/projectile/ProjectileUtil.java 2 | +++ b/net/minecraft/world/entity/projectile/ProjectileUtil.java 3 | @@ -51,9 +_,15 @@ 4 | vec3 = hitResult.getLocation(); 5 | } 6 | 7 | - HitResult entityHitResult = getEntityHitResult( 8 | - level, source, pos, vec3, source.getBoundingBox().expandTowards(deltaMovement).inflate(1.0), filter, margin 9 | - ); 10 | + // Sakura start - configure potion mechanics 11 | + final AABB movementAABB = source.getBoundingBox().expandTowards(deltaMovement).inflate(1.0); 12 | + final HitResult entityHitResult; 13 | + if (level.sakuraConfig().entity.thrownPotion.allowBreakingInsideEntities && source instanceof AbstractThrownPotion) { 14 | + entityHitResult = getEntityHitResult(source, pos, vec3, movementAABB, filter, margin); 15 | + } else { 16 | + entityHitResult = getEntityHitResult(level, source, pos, vec3, movementAABB, filter, margin); 17 | + } 18 | + // Sakura end - configure potion mechanics 19 | if (entityHitResult != null) { 20 | hitResult = entityHitResult; 21 | } 22 | -------------------------------------------------------------------------------- /scripts/upstream.sh: -------------------------------------------------------------------------------- 1 | exit_on_error() { 2 | echo "$1" 3 | exit 1 4 | } 5 | 6 | #git reset HEAD --hard 7 | 8 | oldHash=$(grep "paperRef=" gradle.properties | cut -d "=" -f2) 9 | newHash=$(curl -s https://api.github.com/repos/PaperMC/paper/commits/$1 | jq -r .sha) 10 | 11 | if [ "$oldHash" = "$newHash" ]; then 12 | echo "Upstream has not updated!" 13 | exit 0 14 | fi 15 | 16 | echo "Updating paper: $oldHash -> $newHash" 17 | 18 | sed -i '' "s/$oldHash/$newHash/g" gradle.properties 19 | git add gradle.properties 20 | 21 | ./gradlew cleanCache 22 | ./gradlew applyPaperSingleFilePatchesFuzzy || exit_on_error "An error occurred when applying single file patches!" 23 | ./gradlew rebuildPaperSingleFilePatches || exit_on_error "An error occurred when rebuilding single file patches!" 24 | ./gradlew applyAllPatches || exit_on_error "An error occurred when merging patches!" 25 | ./gradlew rebuildPaperApiPatches || exit_on_error "An error occurred when rebuilding api patches!" 26 | ./gradlew rebuildAllServerPatches || exit_on_error "An error occurred when rebuilding server patches!" 27 | ./gradlew createMojmapPaperclipJar || exit_on_error "An error occurred when building!" 28 | 29 | scripts/upstreamCommit.sh $oldHash $newHash 30 | 31 | echo "Created new commit, please review before pushing." 32 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/BubbleColumnBlock.java 2 | +++ b/net/minecraft/world/level/block/BubbleColumnBlock.java 3 | @@ -50,6 +_,13 @@ 4 | @Override 5 | protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity, InsideBlockEffectApplier effectApplier, boolean pastEdges) { 6 | if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent 7 | + // Sakura start - configure bubble columns affecting cannon entities 8 | + if (!level.sakuraConfig().cannons.tntAndSandAffectedByBubbleColumns && ( 9 | + entity instanceof net.minecraft.world.entity.item.PrimedTnt || 10 | + entity instanceof net.minecraft.world.entity.item.FallingBlockEntity)) { 11 | + return; 12 | + } 13 | + // Sakura end - configure bubble columns affecting cannon entities 14 | if (pastEdges) { 15 | BlockState blockState = level.getBlockState(pos.above()); 16 | boolean flag = blockState.getCollisionShape(level, pos).isEmpty() && blockState.getFluidState().isEmpty(); 17 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/gui/components/ItemButton.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.gui.components; 2 | 3 | import me.samsuik.sakura.player.gui.FeatureGuiInventory; 4 | import org.bukkit.event.inventory.InventoryClickEvent; 5 | import org.bukkit.inventory.Inventory; 6 | import org.bukkit.inventory.ItemStack; 7 | import org.jspecify.annotations.NullMarked; 8 | 9 | @NullMarked 10 | public final class ItemButton implements GuiComponent { 11 | private final ItemStack bukkitItem; 12 | private final int slot; 13 | private final GuiClickEvent whenClicked; 14 | 15 | public ItemButton(final ItemStack bukkitItem, final int slot, final GuiClickEvent whenClicked) { 16 | this.bukkitItem = bukkitItem; 17 | this.slot = slot; 18 | this.whenClicked = whenClicked; 19 | } 20 | 21 | @Override 22 | public boolean interaction(final InventoryClickEvent event, final FeatureGuiInventory featureInventory) { 23 | if (event.getSlot() == this.slot) { 24 | this.whenClicked.doSomething(event, featureInventory); 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | @Override 31 | public void creation(final Inventory inventory) { 32 | inventory.setItem(this.slot, this.bukkitItem); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/projectile/FishingHook.java 2 | +++ b/net/minecraft/world/entity/projectile/FishingHook.java 3 | @@ -303,6 +_,12 @@ 4 | if (!this.level().isClientSide()) { 5 | this.setHookedEntity(result.getEntity()); 6 | } 7 | + // Sakura start - configure entity knockback 8 | + if (this.level().sakuraConfig().players.knockback.fishingHooksApplyKnockback) { 9 | + final Entity entity = result.getEntity(); 10 | + entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0f); 11 | + } 12 | + // Sakura end - configure entity knockback 13 | } 14 | 15 | @Override 16 | @@ -604,7 +_,7 @@ 17 | 18 | public void pullEntity(Entity entity) { 19 | Entity owner = this.getOwner(); 20 | - if (owner != null) { 21 | + if (owner != null && (this.level().sakuraConfig().players.fishingHooksPullEntities || entity instanceof ItemEntity)) { // Sakura - configure fishing hooks pulling entities 22 | Vec3 vec3 = new Vec3(owner.getX() - this.getX(), owner.getY() - this.getY(), owner.getZ() - this.getZ()).scale(0.1); 23 | entity.setDeltaMovement(entity.getDeltaMovement().add(vec3)); 24 | } 25 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/redstone/cache/RedstoneWireUpdate.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.redstone.cache; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import org.jspecify.annotations.NullMarked; 5 | 6 | @NullMarked 7 | public final class RedstoneWireUpdate { 8 | private final BlockPos position; 9 | private final int power; 10 | private final int updateIndex; 11 | private boolean updateShape; 12 | private boolean skipWire; 13 | 14 | public RedstoneWireUpdate(final BlockPos position, final int power, final int updateIndex) { 15 | this.position = position; 16 | this.power = power; 17 | this.updateIndex = updateIndex; 18 | } 19 | 20 | public BlockPos getPosition() { 21 | return this.position; 22 | } 23 | 24 | public int getPower() { 25 | return this.power; 26 | } 27 | 28 | public int getUpdateIndex() { 29 | return this.updateIndex; 30 | } 31 | 32 | public boolean needsShapeUpdate() { 33 | return this.updateShape; 34 | } 35 | 36 | public void updateShapes() { 37 | this.updateShape = true; 38 | } 39 | 40 | public boolean canSkipWireUpdate() { 41 | return this.skipWire; 42 | } 43 | 44 | public void skipWireUpdate() { 45 | this.skipWire = true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0016-Destroy-Waterlogged-Blocks.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Thu, 16 Nov 2023 00:59:04 +0000 4 | Subject: [PATCH] Destroy Waterlogged Blocks 5 | 6 | 7 | diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java 8 | index 976ebf4c62d1cc96f2e147c2240a5886d6b022ed..16575c1222b28f7809db39d320848153e3b97044 100644 9 | --- a/net/minecraft/world/level/ServerExplosion.java 10 | +++ b/net/minecraft/world/level/ServerExplosion.java 11 | @@ -368,6 +368,11 @@ public class ServerExplosion implements Explosion { 12 | if (material != null && material.replaceBlastResistance() && pos.getY() > this.level.getMinY()) { 13 | return Optional.of(material.resistance()); 14 | } 15 | + // Sakura start - destroy water logged blocks 16 | + if (!fluidState.isEmpty() && !blockState.liquid() && this.level.sakuraConfig().cannons.explosion.destroyWaterloggedBlocks) { 17 | + return Optional.of(ZERO_RESISTANCE); 18 | + } 19 | + // Sakura end - destroy water logged blocks 20 | } 21 | 22 | return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState); 23 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0029-Configure-breaking-blocks-outside-the-world-border.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Fri, 7 Mar 2025 17:14:35 +0000 4 | Subject: [PATCH] Configure breaking blocks outside the world border 5 | 6 | 7 | diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java 8 | index 0d3d6b648f1600a91399f5a774a027e8ed5a8226..46cca1d0f0e6e458411924ccba4931a5b4aace1e 100644 9 | --- a/net/minecraft/world/level/ServerExplosion.java 10 | +++ b/net/minecraft/world/level/ServerExplosion.java 11 | @@ -539,6 +539,11 @@ public class ServerExplosion implements Explosion { 12 | return ret; 13 | } 14 | // Sakura end - optimise protected explosions 15 | + // Sakura start - configure breaking blocks when outside the world border 16 | + if (!this.level.sakuraConfig().cannons.explosion.breakBlocksWhenOutsideTheWorldBorder && !this.level.getWorldBorder().isWithinBounds(center)) { 17 | + return ret; 18 | + } 19 | + // Sakura end - configure breaking blocks when outside the world border 20 | 21 | // only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of 22 | // a 16x16x16 cube 23 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java 2 | +++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java 3 | @@ -22,6 +_,12 @@ 4 | this.setPos(x, y, z); 5 | } 6 | 7 | + // Sakura start - enderpearls use outline for collision 8 | + protected net.minecraft.world.level.ClipContext.Block getClipType() { 9 | + return net.minecraft.world.level.ClipContext.Block.COLLIDER; 10 | + } 11 | + // Sakura end - enderpearls use outline for collision 12 | + 13 | @Override 14 | public boolean shouldRenderAtSqrDistance(double distance) { 15 | if (this.tickCount < 2 && distance < 12.25) { 16 | @@ -47,7 +_,7 @@ 17 | this.handleFirstTickBubbleColumn(); 18 | this.applyGravity(); 19 | this.applyInertia(); 20 | - HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); 21 | + HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType()); // Sakura - enderpearls use outline for collision 22 | Vec3 location; 23 | if (hitResultOnMoveVector.getType() != HitResult.Type.MISS) { 24 | location = hitResultOnMoveVector.getLocation(); 25 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/material/LavaFluid.java 2 | +++ b/net/minecraft/world/level/material/LavaFluid.java 3 | @@ -192,12 +_,20 @@ 4 | 5 | @Override 6 | public int getTickDelay(LevelReader level) { 7 | - return level.dimensionType().ultraWarm() ? 10 : 30; 8 | - } 9 | + return level.dimensionType().ultraWarm() && !(level instanceof Level gameLevel && gameLevel.sakuraConfig().environment.disableFastNetherLava) ? 10 : 30; // Sakura - configure fast nether lava 10 | + } 11 | + 12 | + // Sakura start - lava flow speed api 13 | + @Override 14 | + public final int getTickDelay(final Level world, final BlockPos pos) { 15 | + final int flowSpeed = world.localConfig().at(pos).lavaFlowSpeed; 16 | + return flowSpeed >= 0 ? flowSpeed : this.getTickDelay(world); 17 | + } 18 | + // Sakura end - lava flow speed api 19 | 20 | @Override 21 | public int getSpreadDelay(Level level, BlockPos pos, FluidState currentState, FluidState newState) { 22 | - int tickDelay = this.getTickDelay(level); 23 | + int tickDelay = this.getTickDelay(level, pos); // Sakura - lava flow speed api 24 | if (!currentState.isEmpty() 25 | && !newState.isEmpty() 26 | && !currentState.getValue(FALLING) 27 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/player/visibility/VisibilityType.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.visibility; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.jspecify.annotations.NullMarked; 5 | 6 | @NullMarked 7 | public record VisibilityType(String key, ImmutableList states) { 8 | public VisibilityState getDefault() { 9 | return this.states.getFirst(); 10 | } 11 | 12 | public boolean isDefault(final VisibilityState state) { 13 | return state == this.getDefault(); 14 | } 15 | 16 | public VisibilityState cycle(final VisibilityState state) { 17 | final int index = this.states.indexOf(state); 18 | final int next = (index + 1) % this.states.size(); 19 | return this.states.get(next); 20 | } 21 | 22 | public static VisibilityType from(final String key, final boolean minimal) { 23 | return new VisibilityType(key, states(minimal)); 24 | } 25 | 26 | private static ImmutableList states(final boolean minimal) { 27 | final ImmutableList.Builder states = ImmutableList.builder(); 28 | states.add(VisibilityState.ON); 29 | if (minimal) { 30 | states.add(VisibilityState.MINIMAL); 31 | } 32 | states.add(VisibilityState.OFF); 33 | return states.build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/Configurations.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/io/papermc/paper/configuration/Configurations.java 2 | +++ b/src/main/java/io/papermc/paper/configuration/Configurations.java 3 | @@ -95,7 +_,7 @@ 4 | }; 5 | } 6 | 7 | - static CheckedFunction reloader(Class type, T instance) { 8 | + public static CheckedFunction reloader(Class type, T instance) { // Sakura - package-protected -> public 9 | return node -> { 10 | ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type)); 11 | ObjectMapper.Mutable mutable = (ObjectMapper.Mutable) factory.get(type); 12 | @@ -230,7 +_,7 @@ 13 | .path(worldConfigFile) 14 | .build(); 15 | final ConfigurationNode worldNode = worldLoader.load(); 16 | - if (newFile) { // set the version field if new file 17 | + if (newFile && this instanceof PaperConfigurations) { // Sakura - hack this into working // set the version field if new file 18 | worldNode.node(Configuration.VERSION_FIELD).set(this.worldConfigVersion()); 19 | } else { 20 | this.verifyWorldConfigVersion(contextMap, worldNode); 21 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/item/EnderpearlItem.java 2 | +++ b/net/minecraft/world/item/EnderpearlItem.java 3 | @@ -26,6 +_,11 @@ 4 | // Paper start - PlayerLaunchProjectileEvent 5 | final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, serverLevel, itemInHand, player, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F); 6 | com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity()); 7 | + // Sakura start - prevent ender pearls teleporting inside blocks 8 | + if (level.sakuraConfig().entity.enderPearl.preventTeleportingInsideBlocks && player.isInsideAnyCollision()) { 9 | + event.setCancelled(true); 10 | + } 11 | + // Sakura end - prevent ender pearls teleporting inside blocks 12 | if (event.callEvent() && thrownEnderpearl.attemptSpawn()) { 13 | if (event.shouldConsume()) { 14 | itemInHand.consume(1, player); 15 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/item/MilkBucketItem.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.item; 2 | 3 | import me.samsuik.sakura.configuration.GlobalConfiguration; 4 | import net.minecraft.core.component.DataComponentMap; 5 | import net.minecraft.core.component.DataComponents; 6 | import net.minecraft.core.component.PatchedDataComponentMap; 7 | import net.minecraft.world.item.Item; 8 | import org.jspecify.annotations.NullMarked; 9 | 10 | @NullMarked 11 | public final class MilkBucketItem extends Item { 12 | public MilkBucketItem(final Properties properties) { 13 | super(properties); 14 | } 15 | 16 | @Override 17 | public DataComponentMap components() { 18 | final DataComponentMap components = super.components(); 19 | return stackableMilkBuckets() 20 | ? DataComponentHelper.updateBucketMaxStackSize(components) 21 | : components; 22 | } 23 | 24 | @Override 25 | public void modifyComponentsSentToClient(final PatchedDataComponentMap components) { 26 | if (stackableMilkBuckets()) { 27 | components.set(DataComponents.MAX_STACK_SIZE, DataComponentHelper.bucketMaxStackSize()); 28 | } 29 | } 30 | 31 | private static boolean stackableMilkBuckets() { 32 | final GlobalConfiguration config = GlobalConfiguration.get(); 33 | return config != null && config.players.stackableMilkBuckets; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/entity/merge/MergeLevel.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | @NullMarked 6 | public enum MergeLevel { 7 | /** 8 | * Disabled. 9 | */ 10 | NONE(-1), 11 | /** 12 | * "STRICT" merges entities with the same properties, position, momentum and OOE. 13 | * This is considered safe to use, and will not break cannon mechanics. 14 | */ 15 | STRICT(1), 16 | /** 17 | * "LENIENT" merges entities aggressively by tracking the entities that have 18 | * previously merged. This is a hybrid of "SPAWN" and "STRICT" merging, with the 19 | * visuals of "STRICT" merging and better merging potential of "SPAWN" merging. 20 | */ 21 | LENIENT(2), 22 | /** 23 | * "SPAWN" merges entities one gametick after they have spawned. Merging is 24 | * only possible after it has been established that the entity is safe to 25 | * merge by collecting information on the entities that merge together over time. 26 | */ 27 | SPAWN(3); 28 | 29 | private final int level; 30 | 31 | MergeLevel(final int level) { 32 | this.level = level; 33 | } 34 | 35 | public final boolean atLeast(MergeLevel level) { 36 | return this.getLevel() >= level.getLevel(); 37 | } 38 | 39 | public final int getLevel() { 40 | return this.level; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/features/0003-Specialised-Explosions.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Fri, 3 May 2024 15:04:31 +0100 4 | Subject: [PATCH] Specialised Explosions 5 | 6 | 7 | diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java 8 | index ece6db7b9a0dfd535141c0c756947c4898140503..fbe351a877aa0313d522b8b01fc06bcf36d567af 100644 9 | --- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java 10 | +++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java 11 | @@ -42,6 +42,16 @@ public final class IteratorSafeOrderedReferenceSet { 12 | this.listElements = (E[])Array.newInstance(arrComponent, arrayCapacity); 13 | } 14 | 15 | + // Sakura start - specialised explosions; add indexOf and rawGet methods 16 | + public E rawGet(final int index) { 17 | + return this.listElements[index]; 18 | + } 19 | + 20 | + public int indexOf(final E element) { 21 | + return this.indexMap.getInt(element); 22 | + } 23 | + // Sakura end - specialised explosions; add indexOf and rawGet methods 24 | + 25 | // includes null (gravestone) elements 26 | public E[] getListRaw() { 27 | return this.listElements; 28 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command.subcommands; 2 | 3 | import me.samsuik.sakura.command.BaseSubCommand; 4 | import net.kyori.adventure.text.Component; 5 | import net.kyori.adventure.text.format.NamedTextColor; 6 | import net.minecraft.server.MinecraftServer; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.craftbukkit.CraftServer; 9 | import org.jspecify.annotations.NullMarked; 10 | 11 | @NullMarked 12 | public final class ConfigCommand extends BaseSubCommand { 13 | public ConfigCommand(final String name) { 14 | super(name); 15 | this.description = "Command for reloading the sakura configuration file"; 16 | } 17 | 18 | @Override 19 | public void execute(final CommandSender sender, final String[] args) { 20 | sender.sendMessage(Component.text("Please note that this command is not supported and may cause issues.", NamedTextColor.RED)); 21 | sender.sendMessage(Component.text("If you encounter any issues please use the /stop command to restart your server.", NamedTextColor.RED)); 22 | 23 | final MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); 24 | server.sakuraConfigurations.reloadConfigs(server); 25 | server.server.reloadCount++; 26 | 27 | sender.sendMessage(Component.text("Sakura config reload complete.", NamedTextColor.GREEN)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V3_RenameKnockback.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | import org.spongepowered.configurate.NodePath; 5 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 6 | 7 | import java.util.Map; 8 | 9 | import static org.spongepowered.configurate.NodePath.path; 10 | import static org.spongepowered.configurate.transformation.TransformAction.*; 11 | 12 | @NullMarked 13 | public final class V3_RenameKnockback { 14 | private static final int VERSION = 3; 15 | private static final Map RENAME = Map.of( 16 | path("players", "knockback", "vertical-limit-require-ground"), "vertical-knockback-require-ground", 17 | path("players", "knockback", "knockback-horizontal"), "base-knockback" 18 | ); 19 | 20 | private V3_RenameKnockback() {} 21 | 22 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 23 | final ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder(); 24 | for (final Map.Entry entry : RENAME.entrySet()) { 25 | transformationBuilder.addAction(entry.getKey(), rename(entry.getValue())); 26 | } 27 | builder.addVersion(VERSION, transformationBuilder.build()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/LenientStrategy.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge.strategy; 2 | 3 | import me.samsuik.sakura.entity.merge.TrackedMergeHistory; 4 | import me.samsuik.sakura.utils.collections.BlockPosToEntityTable; 5 | import net.minecraft.world.entity.Entity; 6 | import org.jspecify.annotations.NullMarked; 7 | import org.jspecify.annotations.Nullable; 8 | 9 | @NullMarked 10 | final class LenientStrategy implements MergeStrategy { 11 | static final LenientStrategy INSTANCE = new LenientStrategy(); 12 | private final BlockPosToEntityTable entityTable = new BlockPosToEntityTable(128); 13 | 14 | @Override 15 | public boolean trackHistory() { 16 | return true; 17 | } 18 | 19 | @Override 20 | public @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory) { 21 | if (entity.compareState(previous)) { 22 | return previous; 23 | } 24 | 25 | if (!mergeHistory.hasPreviouslyMerged(entity, previous)) { 26 | this.entityTable.clear(); 27 | } 28 | 29 | final Entity nextEntity = this.entityTable.put(entity); 30 | if (nextEntity == null || entity == nextEntity || !nextEntity.level().equals(entity.level())) { 31 | return null; 32 | } 33 | 34 | return entity.compareState(nextEntity) ? nextEntity : null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractThrownPotion.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/projectile/AbstractThrownPotion.java 2 | +++ b/net/minecraft/world/entity/projectile/AbstractThrownPotion.java 3 | @@ -40,6 +_,25 @@ 4 | super(type, x, y, z, level, item); 5 | } 6 | 7 | + // Sakura start - configure potion mechanics 8 | + @Override 9 | + public void shoot(final double x, final double y, final double z, final float speed, final float divergence) { // may be overridden by plugins 10 | + super.shoot(x, y, z, speed, divergence); 11 | + 12 | + final net.minecraft.world.phys.Vec3 movement = this.getDeltaMovement(); 13 | + final double moveX = movement.x * this.level().sakuraConfig().entity.thrownPotion.horizontalSpeed; 14 | + final double moveY = movement.y * this.level().sakuraConfig().entity.thrownPotion.verticalSpeed; 15 | + final double moveZ = movement.z * this.level().sakuraConfig().entity.thrownPotion.horizontalSpeed; 16 | + 17 | + this.setDeltaMovement(moveX, moveY, moveZ); 18 | + } 19 | + 20 | + @Override 21 | + protected final boolean breakInsideOwner() { 22 | + return this.level().sakuraConfig().entity.thrownPotion.allowBreakingInsideEntities && this.tickCount >= 5; 23 | + } 24 | + // Sakura end - configure potion mechanics 25 | + 26 | @Override 27 | protected double getDefaultGravity() { 28 | return 0.05; 29 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command.subcommands; 2 | 3 | import me.samsuik.sakura.command.PlayerOnlySubCommand; 4 | import me.samsuik.sakura.configuration.GlobalConfiguration; 5 | import me.samsuik.sakura.player.visibility.VisibilitySettings; 6 | import me.samsuik.sakura.player.visibility.VisibilityState; 7 | import me.samsuik.sakura.player.visibility.VisibilityType; 8 | import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; 9 | import org.bukkit.entity.Player; 10 | import org.jspecify.annotations.NullMarked; 11 | 12 | import java.util.Arrays; 13 | 14 | @NullMarked 15 | public final class VisualCommand extends PlayerOnlySubCommand { 16 | private final VisibilityType type; 17 | 18 | public VisualCommand(final VisibilityType type, final String... aliases) { 19 | super(type.key() + "visibility"); 20 | this.setAliases(Arrays.asList(aliases)); 21 | this.type = type; 22 | } 23 | 24 | @Override 25 | public void execute(final Player player, final String[] args) { 26 | final VisibilitySettings settings = player.getVisibility(); 27 | final VisibilityState state = settings.toggle(type); 28 | 29 | final String stateName = (state == VisibilityState.ON) ? "Enabled" : "Disabled"; 30 | player.sendMessage(GlobalConfiguration.get().messages.fpsSettingChangeComponent(this.type.key(), stateName)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/item/SpawnEggItem.java 2 | +++ b/net/minecraft/world/item/SpawnEggItem.java 3 | @@ -14,6 +_,7 @@ 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.minecraft.stats.Stats; 6 | import net.minecraft.world.Difficulty; 7 | +import net.minecraft.util.Mth; 8 | import net.minecraft.world.InteractionHand; 9 | import net.minecraft.world.InteractionResult; 10 | import net.minecraft.world.entity.AgeableMob; 11 | @@ -99,6 +_,12 @@ 12 | } else if (!type.isAllowedInPeaceful(stack.get(DataComponents.ENTITY_DATA).getUnsafe()) && level.getDifficulty() == Difficulty.PEACEFUL) { // Paper - check peaceful override 13 | return InteractionResult.FAIL; 14 | } else { 15 | + // Sakura start - prevent placing spawn eggs inside blocks 16 | + if (level.sakuraConfig().players.preventPlacingSpawnEggsInsideBlocks && !level.noCollision(type.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5))) { 17 | + return InteractionResult.PASS; 18 | + } 19 | + // Sakura end - prevent placing spawn eggs inside blocks 20 | + 21 | if (type.spawn((ServerLevel)level, stack, owner, pos, EntitySpawnReason.SPAWN_ITEM_USE, shouldOffsetY, shouldOffsetYMore) != null) { 22 | stack.consume(1, owner); 23 | level.gameEvent(owner, GameEvent.ENTITY_PLACE, pos); 24 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.explosion.durable; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 5 | 6 | @NullMarked 7 | @ConfigSerializable 8 | public record DurableMaterial(int durability, float resistance, boolean onlyDamagedByTnt) { 9 | public DurableMaterial(final int durability, final float resistance) { 10 | this(durability, resistance, true); 11 | } 12 | 13 | public boolean replaceBlastResistance() { 14 | return this.resistance >= 0.0f; 15 | } 16 | 17 | public boolean applyDurability() { 18 | return this.durability >= 0; 19 | } 20 | 21 | public static DurableMaterial durability(final int durability) { 22 | return new DurableMaterial(durability, 0.0f); 23 | } 24 | 25 | public static DurableMaterial resistance(final float resistance) { 26 | return new DurableMaterial(1, resistance); 27 | } 28 | 29 | public static DurableMaterial likeSand(final int durability) { 30 | return new DurableMaterial(durability, 3.0f); 31 | } 32 | 33 | public static DurableMaterial likeCobblestone(final int durability) { 34 | return new DurableMaterial(durability, 6.0f); 35 | } 36 | 37 | public static DurableMaterial likeEndstone(final int durability) { 38 | return new DurableMaterial(durability, 9.0f); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0023-Protect-scaffolding-from-creepers.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Mon, 17 Jun 2024 14:04:12 +0100 4 | Subject: [PATCH] Protect scaffolding from creepers 5 | 6 | 7 | diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java 8 | index 8317c1d8ede33a1de115c5597a439f30284c8b86..a185d44ef797663d5007c9084117ca6933ba46be 100644 9 | --- a/net/minecraft/world/level/ServerExplosion.java 10 | +++ b/net/minecraft/world/level/ServerExplosion.java 11 | @@ -378,6 +378,11 @@ public class ServerExplosion implements Explosion { 12 | return Optional.of(ZERO_RESISTANCE); 13 | } 14 | // Sakura end - allow explosions to destroy lava 15 | + // Sakura start - protect scaffolding from creepers 16 | + if (this.level.sakuraConfig().cannons.explosion.protectScaffoldingFromCreepers && blockState.is(net.minecraft.world.level.block.Blocks.SCAFFOLDING) && this.source instanceof net.minecraft.world.entity.monster.Creeper) { 17 | + return Optional.of(net.minecraft.world.level.block.Blocks.BARRIER.getExplosionResistance()); 18 | + } 19 | + // Sakura end - protect scaffolding from creepers 20 | } 21 | 22 | return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState); 23 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/entity/monster/Creeper.java 2 | +++ b/net/minecraft/world/entity/monster/Creeper.java 3 | @@ -250,7 +_,12 @@ 4 | if (!event.isCancelled()) { 5 | // CraftBukkit end 6 | this.dead = true; 7 | - serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) 8 | + // Sakura start - fix creepers blowing up watered blocks 9 | + // Entities can clip up to 1.0e-7 inside blocks which can be abused to cegg watered blocks. 10 | + // This is patched by adding a slight offset to the explosion position. 11 | + final double explosionY = this.getY() + 1.0e-5; 12 | + serverLevel.explode(this, this.getX(), explosionY, this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) 13 | + // Sakura end - fix creepers blowing up watered blocks 14 | this.spawnLingeringCloud(); 15 | this.triggerOnDeathMobEffects(serverLevel, Entity.RemovalReason.KILLED); 16 | this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause 17 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/chunk/LevelChunkSection.java 2 | +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java 3 | @@ -306,12 +_,18 @@ 4 | 5 | public void fillBiomesFromNoise(BiomeResolver biomeResolver, Climate.Sampler climateSampler, int x, int y, int z) { 6 | PalettedContainer> palettedContainer = this.biomes.recreate(); 7 | + Holder biome = null; // Sakura - calculate biome noise for each chunk section 8 | int i = 4; 9 | 10 | for (int i1 = 0; i1 < 4; i1++) { 11 | for (int i2 = 0; i2 < 4; i2++) { 12 | for (int i3 = 0; i3 < 4; i3++) { 13 | - palettedContainer.getAndSetUnchecked(i1, i2, i3, biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler)); 14 | + // Sakura start - calculate biome noise once for each chunk section 15 | + if (biome == null || !me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.calculateBiomeNoiseOncePerChunkSection) { 16 | + biome = biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler); 17 | + } 18 | + palettedContainer.getAndSetUnchecked(i1, i2, i3, biome); 19 | + // Sakura end - calculate biome noise once for each chunk section 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V9_RenameAllowNonTntBreakingDurableBlocks.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | import org.spongepowered.configurate.NodePath; 5 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 6 | 7 | import static me.samsuik.sakura.configuration.transformation.ConfigurationTransformations.move; 8 | import static me.samsuik.sakura.configuration.transformation.ConfigurationTransformations.newValue; 9 | import static org.spongepowered.configurate.NodePath.*; 10 | 11 | @NullMarked 12 | public final class V9_RenameAllowNonTntBreakingDurableBlocks { 13 | private static final int VERSION = 9; 14 | private static final NodePath EXPLOSION_PATH = path("cannons", "explosion"); 15 | private static final NodePath OLD_PATH = EXPLOSION_PATH.plus(path("allow-non-tnt-breaking-durable-blocks")); 16 | private static final NodePath NEW_PATH = EXPLOSION_PATH.plus(path("require-tnt-to-damage-durable-materials")); 17 | 18 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 19 | final ConfigurationTransformation transform = ConfigurationTransformation.builder() 20 | .addAction(OLD_PATH, move(NEW_PATH)) 21 | .addAction(NEW_PATH, newValue(v -> !v.getBoolean())) 22 | .build(); 23 | builder.addVersion(VERSION, transform); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/redstone/cache/RedstoneNetworkSource.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.redstone.cache; 2 | 3 | import io.papermc.paper.configuration.WorldConfiguration; 4 | import me.samsuik.sakura.configuration.local.CachedLocalConfiguration; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.world.level.Level; 7 | import net.minecraft.world.level.redstone.Orientation; 8 | import org.jspecify.annotations.NullMarked; 9 | import org.jspecify.annotations.Nullable; 10 | 11 | @NullMarked 12 | public record RedstoneNetworkSource( 13 | WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation, 14 | BlockPos position, 15 | @Nullable Orientation orientation, 16 | int updateDepth, 17 | int newPower, 18 | int oldPower 19 | ) { 20 | public static RedstoneNetworkSource createNetworkSource( 21 | final Level level, 22 | final CachedLocalConfiguration localConfiguration, 23 | final BlockPos pos, 24 | final @Nullable Orientation orientation, 25 | final int newPower, 26 | final int oldPower 27 | ) { 28 | final int updateDepth = level.neighborUpdater.getUpdateDepth(); 29 | return new RedstoneNetworkSource(localConfiguration.paperRedstoneImplementation(), pos, orientation, updateDepth, newPower, oldPower); 30 | } 31 | 32 | public boolean isVanilla() { 33 | return this.redstoneImplementation == WorldConfiguration.Misc.RedstoneImplementation.VANILLA; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/mechanics/LegacyBlockFormation.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.mechanics; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.world.level.Level; 5 | import net.minecraft.world.level.material.FlowingFluid; 6 | import net.minecraft.world.level.material.FluidState; 7 | import org.jspecify.annotations.NullMarked; 8 | 9 | @NullMarked 10 | public final class LegacyBlockFormation { 11 | public static boolean canLiquidFormBlock( 12 | final Level level, 13 | final BlockPos pos, 14 | final FluidState fluidState, 15 | final MinecraftMechanicsTarget mechanicsTarget 16 | ) { 17 | // In legacy-paper and versions since 1.16, liquids should always solidify. 18 | if (mechanicsTarget.atLeast(MechanicVersion.v1_16) || mechanicsTarget.before(MechanicVersion.v1_10) && mechanicsTarget.isServerType(ServerType.PAPER)) { 19 | return true; 20 | } 21 | 22 | // In 1.13 and later, liquids can only solidify if they occupy at least half of the block. 23 | if (mechanicsTarget.atLeast(MechanicVersion.v1_13) && fluidState.getHeight(level, pos) >= 0.44444445f) { 24 | return true; 25 | } 26 | 27 | // todo: not sure if this is necessary, this looks identical to the condition above. 28 | if (mechanicsTarget.before(MechanicVersion.v1_13)) { 29 | return FlowingFluid.getLegacyLevel(fluidState) < 4; 30 | } 31 | 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0031-Protect-blocks-above-a-configured-Y-level-from-explo.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Wed, 25 Jun 2025 17:57:44 +0100 4 | Subject: [PATCH] Protect blocks above a configured Y-level from explosions 5 | 6 | 7 | diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java 8 | index 46cca1d0f0e6e458411924ccba4931a5b4aace1e..36a996a5d6be9a92642658574eb04d6d3f4b6b4f 100644 9 | --- a/net/minecraft/world/level/ServerExplosion.java 10 | +++ b/net/minecraft/world/level/ServerExplosion.java 11 | @@ -365,6 +365,11 @@ public class ServerExplosion implements Explosion { 12 | final Block block = blockState.getBlock(); 13 | final me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().at(pos).durableMaterials.get(block); 14 | 15 | + // Sakura start - protect blocks above a configured Y-level from explosions 16 | + if (this.level.sakuraConfig().cannons.explosion.protectBlocksAboveY.test(val -> pos.getY() >= val)) { 17 | + return Optional.of(net.minecraft.world.level.block.Blocks.BARRIER.getExplosionResistance()); 18 | + } 19 | + // Sakura end - protect blocks above a configured Y-level from explosions 20 | if (material != null && material.replaceBlastResistance() && pos.getY() > this.level.getMinY()) { 21 | return Optional.of(material.resistance()); 22 | } 23 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/io/papermc/paper/command/PaperVersionCommand.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/io/papermc/paper/command/PaperVersionCommand.java 2 | +++ b/src/main/java/io/papermc/paper/command/PaperVersionCommand.java 3 | @@ -52,8 +_,10 @@ 4 | final PaperVersionCommand command = new PaperVersionCommand(); 5 | 6 | return Commands.literal("version") 7 | - .requires(source -> source.getSender().hasPermission("bukkit.command.version")) 8 | + // Sakura start - customise version command 9 | .then(Commands.argument("plugin", StringArgumentType.word()) 10 | + .requires(source -> source.getSender().hasPermission("bukkit.command.version")) 11 | + // Sakura end - customise version command 12 | .suggests(command::suggestPlugins) 13 | .executes(command::pluginVersion)) 14 | .executes(command::serverVersion) 15 | @@ -130,7 +_,13 @@ 16 | } 17 | 18 | private int serverVersion(CommandContext context) { 19 | - sendVersion(context.getSource().getSender()); 20 | + // Sakura start - customise version command 21 | + final CommandSender sender = context.getSource().getSender(); 22 | + //if (sender.hasPermission("bukkit.command.version")) { 23 | + // sendVersion(sender); 24 | + //} 25 | + me.samsuik.sakura.configuration.SakuraVersionInformation.sendVersionToPlayer(sender); 26 | + // Sakura end - customise version command 27 | return Command.SINGLE_SUCCESS; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V1_RelocateMessages.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.global; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | import org.spongepowered.configurate.NodePath; 5 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 6 | import org.spongepowered.configurate.transformation.TransformAction; 7 | 8 | import java.util.Map; 9 | 10 | import static org.spongepowered.configurate.NodePath.path; 11 | 12 | @NullMarked 13 | public final class V1_RelocateMessages { 14 | private static final int VERSION = 2; // targeted version is always ahead by one 15 | private static final Map RELOCATION = Map.of( 16 | path("fps", "message"), path("messages", "fps-setting-change"), 17 | path("players", "potato-message"), path("messages", "durable-block-interaction") 18 | ); 19 | 20 | private V1_RelocateMessages() {} 21 | 22 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 23 | final ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder(); 24 | for (final Map.Entry entry : RELOCATION.entrySet()) { 25 | transformationBuilder.addAction(entry.getKey(), relocate(entry.getValue())); 26 | } 27 | builder.addVersion(VERSION, transformationBuilder.build()); 28 | } 29 | 30 | private static TransformAction relocate(final NodePath path) { 31 | return (node, object) -> path.array(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V8_RenameExplosionResistantItems.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | import org.spongepowered.configurate.NodePath; 5 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 6 | 7 | import static me.samsuik.sakura.configuration.transformation.ConfigurationTransformations.move; 8 | import static org.spongepowered.configurate.NodePath.path; 9 | 10 | @NullMarked 11 | public final class V8_RenameExplosionResistantItems { 12 | private static final int VERSION = 8; 13 | private static final NodePath ITEMS_PATH = path("entity", "items"); 14 | private static final NodePath OLD_WHITELIST_PATH = ITEMS_PATH.plus(path("use-whitelist-for-explosion-resistant-items")); 15 | private static final NodePath NEW_WHITELIST_PATH = ITEMS_PATH.plus(path("blast-resistant", "whitelist-over-blacklist")); 16 | private static final NodePath OLD_ITEMS_PATH = ITEMS_PATH.plus(path("explosion-resistant-items")); 17 | private static final NodePath NEW_ITEMS_PATH = ITEMS_PATH.plus(path("blast-resistant", "items")); 18 | 19 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 20 | final ConfigurationTransformation transform = ConfigurationTransformation.builder() 21 | .addAction(OLD_WHITELIST_PATH, move(NEW_WHITELIST_PATH)) 22 | .addAction(OLD_ITEMS_PATH, move(NEW_ITEMS_PATH)) 23 | .build(); 24 | builder.addVersion(VERSION, transform); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V2_ConvertIconToMaterial.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.global; 2 | 3 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 4 | import org.jspecify.annotations.NullMarked; 5 | import org.jspecify.annotations.Nullable; 6 | import org.spongepowered.configurate.ConfigurateException; 7 | import org.spongepowered.configurate.ConfigurationNode; 8 | import org.spongepowered.configurate.NodePath; 9 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 10 | import org.spongepowered.configurate.transformation.TransformAction; 11 | 12 | @NullMarked 13 | public final class V2_ConvertIconToMaterial implements TransformAction { 14 | private static final int VERSION = 3; // targeted version is always ahead by one 15 | private static final NodePath FPS_MATERIAL_PATH = NodePath.path("fps", "material"); 16 | private static final V2_ConvertIconToMaterial INSTANCE = new V2_ConvertIconToMaterial(); 17 | 18 | private V2_ConvertIconToMaterial() {} 19 | 20 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 21 | builder.addVersion(VERSION, ConfigurationTransformations.transform(FPS_MATERIAL_PATH, INSTANCE)); 22 | } 23 | 24 | @Override 25 | public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { 26 | if (value.raw() instanceof String stringValue) { 27 | value.raw(stringValue.toUpperCase()); 28 | } 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java 2 | +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java 3 | @@ -992,6 +_,7 @@ 4 | 5 | org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot 6 | this.console.paperConfigurations.reloadConfigs(this.console); 7 | + this.console.sakuraConfigurations.reloadConfigs(this.console); // Sakura - sakura configuration files; missing comment above :< 8 | for (ServerLevel world : this.console.getAllLevels()) { 9 | // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty 10 | world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && world.getGameRules().getBoolean(GameRules.RULE_SPAWN_MONSTERS)); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) 11 | @@ -1023,6 +_,7 @@ 12 | this.reloadData(); 13 | org.spigotmc.SpigotConfig.registerCommands(); // Spigot 14 | io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper 15 | + me.samsuik.sakura.command.SakuraCommands.registerCommands(this.console); // Sakura - sakura configuration files 16 | this.spark.registerCommandBeforePlugins(this); // Paper - spark 17 | this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); 18 | this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); 19 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/item/PowderedSnowBucketItem.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.item; 2 | 3 | import me.samsuik.sakura.configuration.GlobalConfiguration; 4 | import net.minecraft.core.component.DataComponentMap; 5 | import net.minecraft.core.component.DataComponents; 6 | import net.minecraft.core.component.PatchedDataComponentMap; 7 | import net.minecraft.sounds.SoundEvents; 8 | import net.minecraft.world.item.SolidBucketItem; 9 | import net.minecraft.world.level.block.Blocks; 10 | import org.jspecify.annotations.NullMarked; 11 | 12 | @NullMarked 13 | public final class PowderedSnowBucketItem extends SolidBucketItem { 14 | public PowderedSnowBucketItem(final Properties properties) { 15 | super(Blocks.POWDER_SNOW, SoundEvents.BUCKET_EMPTY_POWDER_SNOW, properties); 16 | } 17 | 18 | @Override 19 | public DataComponentMap components() { 20 | final DataComponentMap components = super.components(); 21 | return stackablePowderedSnowBuckets() 22 | ? DataComponentHelper.updateBucketMaxStackSize(components) 23 | : components; 24 | } 25 | 26 | @Override 27 | public void modifyComponentsSentToClient(final PatchedDataComponentMap components) { 28 | if (stackablePowderedSnowBuckets()) { 29 | components.set(DataComponents.MAX_STACK_SIZE, DataComponentHelper.bucketMaxStackSize()); 30 | } 31 | } 32 | 33 | private static boolean stackablePowderedSnowBuckets() { 34 | final GlobalConfiguration config = GlobalConfiguration.get(); 35 | return config != null && config.players.stackablePowderedSnowBuckets; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/server/level/ChunkMap.java 2 | +++ b/net/minecraft/server/level/ChunkMap.java 3 | @@ -134,7 +_,7 @@ 4 | public final ChunkMap.DistanceManager distanceManager; 5 | private final String storageName; 6 | private final PlayerMap playerMap = new PlayerMap(); 7 | - public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); 8 | + public final Int2ObjectMap entityMap = new me.samsuik.sakura.utils.collections.TrackedEntityChunkMap(); // Sakura - optimised tracked entity map 9 | private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); 10 | // Paper - rewrite chunk system 11 | public int serverViewDistance; 12 | @@ -1330,7 +_,10 @@ 13 | double vec3_dz = player.getZ() - this.entity.getZ(); 14 | // Paper end - remove allocation of Vec3D here 15 | int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player); 16 | - double d = Math.min(this.getEffectiveRange(), playerViewDistance * 16); 17 | + // Sakura start - entity tracking range modifier 18 | + final double visibleRange = this.getEffectiveRange() * player.trackingRangeModifier; 19 | + final double d = Math.min(visibleRange, playerViewDistance * 16); 20 | + // Sakura end - entity tracking range modifier 21 | double d1 = vec3_dx * vec3_dx + vec3_dz * vec3_dz; // Paper 22 | double d2 = d * d; 23 | // Paper start - Configurable entity tracking range by Y 24 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/server/level/ServerPlayer.java 2 | +++ b/net/minecraft/server/level/ServerPlayer.java 3 | @@ -308,7 +_,7 @@ 4 | // Paper start - Sync offhand slot in menus 5 | @Override 6 | public void sendOffHandSlotChange() { 7 | - this.sendSlotChange(ServerPlayer.this.inventoryMenu, net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy()); 8 | + this.sendSlotChange(ServerPlayer.this.inventoryMenu, net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copyForPacket()); // Sakura - modify components sent to the client 9 | } 10 | // Paper end - Sync offhand slot in menus 11 | 12 | @@ -443,6 +_,7 @@ 13 | public boolean isRealPlayer; // Paper 14 | public @Nullable com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent 15 | public @Nullable org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event 16 | + public double trackingRangeModifier = 1.0; // Sakura - entity tracking range modifier 17 | 18 | // Paper start - rewrite chunk system 19 | private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; 20 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V6_FixIncorrectExtraKnockback.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 4 | import org.jspecify.annotations.NullMarked; 5 | import org.jspecify.annotations.Nullable; 6 | import org.spongepowered.configurate.ConfigurateException; 7 | import org.spongepowered.configurate.ConfigurationNode; 8 | import org.spongepowered.configurate.NodePath; 9 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 10 | import org.spongepowered.configurate.transformation.TransformAction; 11 | 12 | import static org.spongepowered.configurate.NodePath.path; 13 | 14 | @NullMarked 15 | public final class V6_FixIncorrectExtraKnockback implements TransformAction { 16 | private static final int VERSION = 6; 17 | private static final NodePath EXTRA_KNOCKBACK_PATH = path("players", "knockback", "sprinting", "extra-knockback"); 18 | private static final V6_FixIncorrectExtraKnockback INSTANCE = new V6_FixIncorrectExtraKnockback(); 19 | 20 | private V6_FixIncorrectExtraKnockback() {} 21 | 22 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 23 | builder.addVersion(VERSION, ConfigurationTransformations.transform(EXTRA_KNOCKBACK_PATH, INSTANCE)); 24 | } 25 | 26 | @Override 27 | public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { 28 | if (value.getDouble() == 1.0) { 29 | value.set(0.5); 30 | } 31 | return null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java.patch: -------------------------------------------------------------------------------- 1 | --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java 2 | +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java 3 | @@ -178,7 +_,7 @@ 4 | .defaultOptions(PaperConfigurations::defaultOptions); 5 | } 6 | 7 | - private static ConfigurationOptions defaultOptions(ConfigurationOptions options) { 8 | + public static ConfigurationOptions defaultOptions(ConfigurationOptions options) { // Sakura - sakura configuration files; private -> public 9 | return options.serializers(builder -> builder 10 | .register(MapSerializer.TYPE, new MapSerializer(false)) 11 | .register(new EnumValueSerializer()) 12 | @@ -340,7 +_,7 @@ 13 | } 14 | } 15 | 16 | - private static List>> defaultFieldProcessors() { 17 | + public static List>> defaultFieldProcessors() { // Sakura - sakura configuration files; private -> public 18 | return List.of( 19 | MergeMap.DEFINITION 20 | ); 21 | @@ -482,7 +_,7 @@ 22 | } 23 | 24 | // Symlinks are not correctly checked in createDirectories 25 | - static void createDirectoriesSymlinkAware(Path path) throws IOException { 26 | + public static void createDirectoriesSymlinkAware(Path path) throws IOException { // Sakura - sakura configuration files; package-protected -> public 27 | if (!Files.isDirectory(path)) { 28 | Files.createDirectories(path); 29 | } 30 | -------------------------------------------------------------------------------- /sakura-api/paper-patches/features/0002-Merge-Cannon-Entities-API.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Sat, 9 Sep 2023 18:39:15 +0100 4 | Subject: [PATCH] Merge Cannon Entities API 5 | 6 | 7 | diff --git a/src/main/java/org/bukkit/entity/FallingBlock.java b/src/main/java/org/bukkit/entity/FallingBlock.java 8 | index 3a7883629eb5bff5eef94389590d579a79a8e5d3..748dace28d791e6be6c5da8e21463d3b47cbeff5 100644 9 | --- a/src/main/java/org/bukkit/entity/FallingBlock.java 10 | +++ b/src/main/java/org/bukkit/entity/FallingBlock.java 11 | @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; 12 | /** 13 | * Represents a falling block 14 | */ 15 | -public interface FallingBlock extends Entity { 16 | +public interface FallingBlock extends Entity, me.samsuik.sakura.entity.merge.Mergeable { // Sakura - merge cannon entities api 17 | 18 | // Sakura start - falling block height parity api 19 | /** 20 | diff --git a/src/main/java/org/bukkit/entity/TNTPrimed.java b/src/main/java/org/bukkit/entity/TNTPrimed.java 21 | index d97674b5f9269b0a952af7ba3ac86c8c92f1b81c..7a2de90d3340d6bfbaac545e7108a26340b5b753 100644 22 | --- a/src/main/java/org/bukkit/entity/TNTPrimed.java 23 | +++ b/src/main/java/org/bukkit/entity/TNTPrimed.java 24 | @@ -6,7 +6,7 @@ import org.jetbrains.annotations.Nullable; 25 | /** 26 | * Represents a Primed TNT. 27 | */ 28 | -public interface TNTPrimed extends Explosive { 29 | +public interface TNTPrimed extends Explosive, me.samsuik.sakura.entity.merge.Mergeable { // Sakura - merge cannon entities api 30 | 31 | /** 32 | * Set the number of ticks until the TNT blows up after being primed. 33 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraVersionInformation.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.event.HoverEvent; 5 | import net.kyori.adventure.text.format.NamedTextColor; 6 | import net.kyori.adventure.text.minimessage.MiniMessage; 7 | import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.command.CommandSender; 10 | import org.jspecify.annotations.NullMarked; 11 | 12 | @NullMarked 13 | public final class SakuraVersionInformation { 14 | private static final String VERSION_MESSAGE = """ 15 | . 16 | | This server is running Sakura 17 | | Commit: \\<> targeting (MC: ) 18 | | Github: \\<link> 19 | '"""; 20 | 21 | public static void sendVersionToPlayer(final CommandSender sender) { 22 | sender.sendMessage(MiniMessage.miniMessage().deserialize(VERSION_MESSAGE, 23 | Placeholder.component("commit", gitCommit()), 24 | Placeholder.unparsed("version", Bukkit.getMinecraftVersion()) 25 | )); 26 | } 27 | 28 | private static Component gitCommit() { 29 | return Component.text("hover", NamedTextColor.YELLOW) 30 | .hoverEvent(HoverEvent.showText(Component.text(Bukkit.getGitInformation()))); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/explosion/SortExplosionRays.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.explosion; 2 | 3 | import it.unimi.dsi.fastutil.doubles.DoubleArrayList; 4 | import org.jspecify.annotations.NullMarked; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Comparator; 8 | import java.util.List; 9 | 10 | /** 11 | * Sort explosion rays for better cache utilisation. 12 | *
13 |  *   x +   Vanilla     Sorted
14 |  * z @ z      8           5
15 |  * - x      6   7       6   4
16 |  *        4   @   5   7   @   3
17 |  *          2   3       8   2
18 |  *            1           1
19 |  * 
20 | */ 21 | @NullMarked 22 | public final class SortExplosionRays { 23 | private static final Comparator EXPLOSION_RAY_COMPARATOR = Comparator.comparingDouble(vec -> { 24 | final double sign = Math.signum(vec[0]); 25 | final double dir = (sign - 1) / 2; 26 | return sign + 8 + vec[2] * dir; 27 | }); 28 | 29 | public static double[] sortExplosionRays(final DoubleArrayList rayCoords) { 30 | final List explosionRays = new ArrayList<>(); 31 | for (int index = 0; index < rayCoords.size(); index += 3) { 32 | final double[] vector = new double[3]; 33 | rayCoords.getElements(index, vector, 0, 3); 34 | explosionRays.add(vector); 35 | } 36 | 37 | rayCoords.clear(); 38 | explosionRays.sort(EXPLOSION_RAY_COMPARATOR); 39 | 40 | final double[] rays = new double[explosionRays.size() * 3]; 41 | for (int i = 0; i < explosionRays.size() * 3; i++) { 42 | rays[i] = explosionRays.get(i / 3)[i % 3]; 43 | } 44 | return rays; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/item/DataComponentHelper.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.item; 2 | 3 | import me.samsuik.sakura.configuration.GlobalConfiguration; 4 | import net.minecraft.core.component.DataComponentMap; 5 | import net.minecraft.core.component.DataComponentType; 6 | import net.minecraft.core.component.DataComponents; 7 | import net.minecraft.world.item.ItemStack; 8 | import org.jspecify.annotations.NullMarked; 9 | 10 | @NullMarked 11 | public final class DataComponentHelper { 12 | public static int bucketMaxStackSize() { 13 | final GlobalConfiguration config = GlobalConfiguration.get(); 14 | return config != null && config.players.bucketStackSize.isDefined() 15 | ? config.players.bucketStackSize.intValue() 16 | : -1; 17 | } 18 | 19 | public static DataComponentMap updateBucketMaxStackSize(final DataComponentMap components) { 20 | return copyComponentsAndModifyMaxStackSize(components, bucketMaxStackSize()); 21 | } 22 | 23 | @SuppressWarnings("OptionalAssignedToNull") 24 | public static boolean itemHasComponent(final ItemStack stack, final DataComponentType component) { 25 | return stack.getComponentsPatch().get(component) != null; 26 | } 27 | 28 | public static DataComponentMap copyComponentsAndModifyMaxStackSize(final DataComponentMap componentMap, final int maxItemSize) { 29 | if (maxItemSize > 1 && maxItemSize <= 99) { 30 | return DataComponentMap.builder() 31 | .addAll(componentMap) 32 | .set(DataComponents.MAX_STACK_SIZE, maxItemSize) 33 | .build(); 34 | } 35 | return componentMap; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/EntityState.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.world.entity.Entity; 5 | import net.minecraft.world.level.block.Blocks; 6 | import net.minecraft.world.phys.AABB; 7 | import net.minecraft.world.phys.Vec3; 8 | import org.jspecify.annotations.NullMarked; 9 | 10 | import java.util.Optional; 11 | 12 | @NullMarked 13 | public record EntityState( 14 | Vec3 position, 15 | Vec3 momentum, 16 | AABB bb, 17 | Vec3 stuckSpeed, 18 | Optional supportingPos, 19 | boolean onGround, 20 | double fallDistance 21 | ) { 22 | public static EntityState of(final Entity entity) { 23 | return new EntityState( 24 | entity.position(), 25 | entity.getDeltaMovement(), 26 | entity.getBoundingBox(), 27 | entity.stuckSpeedMultiplier, 28 | entity.mainSupportingBlockPos, 29 | entity.onGround(), 30 | entity.fallDistance 31 | ); 32 | } 33 | 34 | public void apply(final Entity entity) { 35 | entity.setPos(this.position); 36 | entity.setDeltaMovement(this.momentum); 37 | entity.setBoundingBox(this.bb); 38 | entity.makeStuckInBlock(Blocks.AIR.defaultBlockState(), this.stuckSpeed); 39 | entity.onGround = this.onGround; 40 | entity.mainSupportingBlockPos = this.supportingPos; 41 | entity.fallDistance = this.fallDistance; 42 | } 43 | 44 | public boolean comparePositionAndMotion(Entity entity) { 45 | return entity.position().equals(this.position) 46 | && entity.getDeltaMovement().equals(this.momentum); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/MergeEntityData.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge; 2 | 3 | import it.unimi.dsi.fastutil.longs.LongOpenHashSet; 4 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 5 | import net.minecraft.world.entity.Entity; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | import java.util.List; 9 | 10 | @NullMarked 11 | public final class MergeEntityData { 12 | private final Entity entity; 13 | private final List connected = new ObjectArrayList<>(); 14 | public int count = 1; 15 | public MergeLevel mergeLevel = MergeLevel.NONE; 16 | 17 | public MergeEntityData(final Entity entity) { 18 | this.entity = entity; 19 | } 20 | 21 | private void updateEntityHandles(final Entity entity) { 22 | for (final MergeEntityData entityData : this.connected) { 23 | entityData.entity.updateBukkitHandle(entity); 24 | } 25 | } 26 | 27 | public void mergeWith(final MergeEntityData mergeEntityData) { 28 | this.connected.add(mergeEntityData); 29 | this.connected.addAll(mergeEntityData.connected); 30 | this.count += mergeEntityData.count; 31 | mergeEntityData.updateEntityHandles(this.entity); 32 | mergeEntityData.count = 0; 33 | mergeEntityData.connected.clear(); 34 | } 35 | 36 | public boolean hasMerged() { 37 | return !this.connected.isEmpty() && this.count != 0; 38 | } 39 | 40 | public LongOpenHashSet getOriginPositions() { 41 | final LongOpenHashSet positions = new LongOpenHashSet(); 42 | this.connected.forEach(entityData -> positions.add(entityData.entity.getPackedOriginPosition())); 43 | return positions; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/explosion/density/CachedBlockDensity.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.explosion.density; 2 | 3 | import net.minecraft.world.entity.Entity; 4 | import net.minecraft.world.phys.AABB; 5 | import net.minecraft.world.phys.Vec3; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | @NullMarked 9 | public final class CachedBlockDensity { 10 | private AABB source; 11 | private AABB entity; 12 | private final float blockDensity; 13 | private final boolean complete; 14 | 15 | public CachedBlockDensity(final Vec3 explosionPos, final Entity entity, final float blockDensity) { 16 | this.source = new AABB(explosionPos, explosionPos); 17 | this.entity = entity.getBoundingBox(); 18 | this.blockDensity = blockDensity; 19 | this.complete = blockDensity == 0.0f || blockDensity == 1.0f; 20 | } 21 | 22 | public float blockDensity() { 23 | return this.blockDensity; 24 | } 25 | 26 | public boolean complete() { 27 | return this.complete; 28 | } 29 | 30 | public boolean hasPosition(final Vec3 explosionPos, final AABB entityBoundingBox) { 31 | return this.isExplosionPosition(explosionPos) && this.entity.containsInclusive(entityBoundingBox); 32 | } 33 | 34 | public boolean isKnownPosition(final Vec3 pos) { 35 | return this.entity.containsInclusive(pos); 36 | } 37 | 38 | public boolean isExplosionPosition(final Vec3 explosionPos) { 39 | return this.source.containsInclusive(explosionPos); 40 | } 41 | 42 | public void expand(final Vec3 explosionPos, final Entity entity) { 43 | this.source = this.source.expand(explosionPos); 44 | this.entity = this.entity.minmax(entity.getBoundingBox()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V2_VerticalKnockbackUseDefault.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import io.papermc.paper.configuration.type.number.DoubleOr; 4 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 5 | import org.jspecify.annotations.NullMarked; 6 | import org.jspecify.annotations.Nullable; 7 | import org.spongepowered.configurate.ConfigurateException; 8 | import org.spongepowered.configurate.ConfigurationNode; 9 | import org.spongepowered.configurate.NodePath; 10 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 11 | import org.spongepowered.configurate.transformation.TransformAction; 12 | 13 | import static org.spongepowered.configurate.NodePath.path; 14 | 15 | @NullMarked 16 | public final class V2_VerticalKnockbackUseDefault implements TransformAction { 17 | private static final int VERSION = 2; 18 | private static final NodePath KNOCKBACK_VERTICAL_PATH = path("players", "knockback", "knockback-vertical"); 19 | private static final V2_VerticalKnockbackUseDefault INSTANCE = new V2_VerticalKnockbackUseDefault(); 20 | 21 | private V2_VerticalKnockbackUseDefault() {} 22 | 23 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 24 | builder.addVersion(VERSION, ConfigurationTransformations.transform(KNOCKBACK_VERTICAL_PATH, INSTANCE)); 25 | } 26 | 27 | @Override 28 | public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { 29 | if (value.getDouble() == 0.4) { 30 | value.set(DoubleOr.Default.USE_DEFAULT); 31 | } 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/player/visibility/VisibilitySettings.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.visibility; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | 5 | @NullMarked 6 | public interface VisibilitySettings { 7 | default boolean isEnabled(final VisibilityType type) { 8 | return this.get(type) == VisibilityState.ON; 9 | } 10 | 11 | default boolean isDisabled(final VisibilityType type) { 12 | return this.get(type) == VisibilityState.OFF; 13 | } 14 | 15 | default boolean isToggled(final VisibilityType type) { 16 | return !type.isDefault(this.get(type)); 17 | } 18 | 19 | default VisibilityState toggle(final VisibilityType type) { 20 | final VisibilityState state = this.get(type); 21 | return this.set(type, toggleState(state)); 22 | } 23 | 24 | default VisibilityState cycle(final VisibilityType type) { 25 | final VisibilityState state = this.get(type); 26 | return this.set(type, type.cycle(state)); 27 | } 28 | 29 | default void toggleAll() { 30 | final VisibilityState state = this.currentState(); 31 | final VisibilityState newState = toggleState(state); 32 | for (final VisibilityType type : VisibilityTypes.types()) { 33 | this.set(type, newState); 34 | } 35 | } 36 | 37 | VisibilityState get(final VisibilityType type); 38 | 39 | VisibilityState set(final VisibilityType type, final VisibilityState state); 40 | 41 | VisibilityState currentState(); 42 | 43 | boolean playerModified(); 44 | 45 | static VisibilityState toggleState(final VisibilityState state) { 46 | return state != VisibilityState.OFF 47 | ? VisibilityState.OFF 48 | : VisibilityState.ON; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/entity/merge/strategy/MergeStrategy.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.entity.merge.strategy; 2 | 3 | import me.samsuik.sakura.entity.merge.MergeLevel; 4 | import me.samsuik.sakura.entity.merge.TrackedMergeHistory; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.level.entity.EntityTickList; 7 | import org.jspecify.annotations.NullMarked; 8 | import org.jspecify.annotations.Nullable; 9 | 10 | @NullMarked 11 | public interface MergeStrategy { 12 | /** 13 | * If this merge strategy requires the merge history to be tracked. 14 | * 15 | * @return should track history 16 | */ 17 | boolean trackHistory(); 18 | 19 | /** 20 | * Tries to merge the first entity into another entity. 21 | *

22 | * The first entity should always be positioned right after the second entity in the 23 | * {@link EntityTickList}. This method should only 24 | * be called before the first entity and after the second entity has ticked. 25 | * 26 | * @param entity current entity 27 | * @param previous last entity to tick 28 | * @return success 29 | */ 30 | @Nullable Entity mergeEntity(final Entity entity, final Entity previous, final TrackedMergeHistory mergeHistory); 31 | 32 | /** 33 | * Gets the {@link MergeStrategy} for the {@link MergeLevel}. 34 | * 35 | * @param level provided level 36 | * @return strategy 37 | */ 38 | static MergeStrategy from(final MergeLevel level) { 39 | return switch (level) { 40 | case NONE -> NoneStrategy.INSTANCE; 41 | case STRICT -> StrictStrategy.INSTANCE; 42 | case LENIENT -> LenientStrategy.INSTANCE; 43 | case SPAWN -> SpawnStrategy.INSTANCE; 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/utils/collections/BlockPosToEntityTable.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.utils.collections; 2 | 3 | import it.unimi.dsi.fastutil.HashCommon; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.world.entity.Entity; 6 | import org.jspecify.annotations.NullMarked; 7 | import org.jspecify.annotations.Nullable; 8 | 9 | import java.util.Arrays; 10 | 11 | @NullMarked 12 | public final class BlockPosToEntityTable { 13 | private final @Nullable Entity[] entities; 14 | private final int mask; 15 | 16 | public BlockPosToEntityTable(final int expectedSize) { 17 | if (expectedSize < 0) { 18 | throw new IllegalArgumentException("Table size cannot be negative"); 19 | } else { 20 | final int size = HashCommon.nextPowerOfTwo(expectedSize - 1); 21 | this.entities = new Entity[size]; 22 | this.mask = (size - 1); 23 | } 24 | } 25 | 26 | public @Nullable Entity get(final BlockPos blockPos) { 27 | return this.entities[this.key(blockPos)]; 28 | } 29 | 30 | public @Nullable Entity put(final Entity entity) { 31 | return this.put(entity.blockPosition(), entity); 32 | } 33 | 34 | public @Nullable Entity put(final BlockPos blockPos, final @Nullable Entity entity) { 35 | final int index = this.key(blockPos); 36 | final Entity present = this.entities[index]; 37 | this.entities[index] = entity; 38 | return present; 39 | } 40 | 41 | public @Nullable Entity remove(final BlockPos blockPos) { 42 | return this.put(blockPos, null); 43 | } 44 | 45 | public void clear() { 46 | Arrays.fill(this.entities, null); 47 | } 48 | 49 | private int key(final BlockPos blockPos) { 50 | return blockPos.hashCode() & this.mask; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.explosion.durable; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import net.minecraft.core.BlockPos; 6 | import org.jspecify.annotations.NullMarked; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | @NullMarked 11 | public final class DurableBlockManager { 12 | private final Cache durableBlocks = CacheBuilder.newBuilder() 13 | .expireAfterAccess(1, TimeUnit.MINUTES) 14 | .maximumSize(Short.MAX_VALUE) 15 | .build(); 16 | 17 | public boolean damage(final BlockPos blockPos, final DurableMaterial material) { 18 | DurableBlock block = this.durableBlocks.getIfPresent(blockPos); 19 | if (block == null) { 20 | this.durableBlocks.put(blockPos, block = new DurableBlock(material.durability())); 21 | } 22 | 23 | final boolean destroyedBlock = block.damage(); 24 | if (destroyedBlock) { 25 | this.durableBlocks.invalidate(blockPos); 26 | } 27 | 28 | return destroyedBlock; 29 | } 30 | 31 | public int durability(final BlockPos pos, final DurableMaterial material) { 32 | final DurableBlock block = this.durableBlocks.getIfPresent(pos); 33 | return block != null ? block.durability() : material.durability(); 34 | } 35 | 36 | private static final class DurableBlock { 37 | private int durability; 38 | 39 | public DurableBlock(final int durability) { 40 | this.durability = durability; 41 | } 42 | 43 | public int durability() { 44 | return this.durability; 45 | } 46 | 47 | public boolean damage() { 48 | return --this.durability <= 0; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/gui/components/ItemSwitch.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.gui.components; 2 | 3 | import com.google.common.base.Preconditions; 4 | import me.samsuik.sakura.player.gui.FeatureGuiInventory; 5 | import org.bukkit.event.inventory.InventoryClickEvent; 6 | import org.bukkit.inventory.Inventory; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.jspecify.annotations.NullMarked; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | @NullMarked 14 | public final class ItemSwitch implements GuiComponent { 15 | private final List items; 16 | private final int slot; 17 | private final int selected; 18 | private final GuiClickEvent whenClicked; 19 | 20 | public ItemSwitch(final List items, final int slot, final int selected, final GuiClickEvent whenClicked) { 21 | Preconditions.checkArgument(!items.isEmpty()); 22 | this.items = Collections.unmodifiableList(items); 23 | this.slot = slot; 24 | this.selected = selected; 25 | this.whenClicked = whenClicked; 26 | } 27 | 28 | @Override 29 | public boolean interaction(final InventoryClickEvent event, final FeatureGuiInventory featureInventory) { 30 | if (this.slot == event.getSlot()) { 31 | final int next = (this.selected + 1) % this.items.size(); 32 | final ItemSwitch itemSwitch = new ItemSwitch(this.items, this.slot, next, this.whenClicked); 33 | featureInventory.replaceComponent(this, itemSwitch); 34 | this.whenClicked.doSomething(event, featureInventory); 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | @Override 41 | public void creation(final Inventory inventory) { 42 | inventory.setItem(this.slot, this.items.get(this.selected)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java 2 | +++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java 3 | @@ -46,6 +_,11 @@ 4 | private float progressO = 0.0F; 5 | private long lastTicked; 6 | private int deathTicks; 7 | + // Sakura start - cache moving block entity collision shape 8 | + private VoxelShape collisionShape = Shapes.empty(); 9 | + private Direction shapeDirection = null; 10 | + private float shapeProgress = Float.MIN_VALUE; 11 | + // Sakura end - cache moving block entity collision shape 12 | 13 | public PistonMovingBlockEntity(BlockPos pos, BlockState blockState) { 14 | super(BlockEntityType.PISTON, pos, blockState); 15 | @@ -360,6 +_,18 @@ 16 | } 17 | 18 | public VoxelShape getCollisionShape(BlockGetter level, BlockPos pos) { 19 | + // Sakura start - cache moving block entity collision shape 20 | + Direction direction = NOCLIP.get(); 21 | + if (this.progress == this.shapeProgress && direction == this.shapeDirection) { 22 | + return this.collisionShape; 23 | + } else { 24 | + this.shapeProgress = this.progress; 25 | + this.shapeDirection = direction; 26 | + return this.collisionShape = this.createCollisionShape(level, pos); 27 | + } 28 | + } 29 | + private VoxelShape createCollisionShape(final BlockGetter level, final BlockPos pos) { 30 | + // Sakura end - cache moving block entity collision shape 31 | VoxelShape collisionShape; 32 | if (!this.extending && this.isSourcePiston && this.movedState.getBlock() instanceof PistonBaseBlock) { 33 | collisionShape = this.movedState.setValue(PistonBaseBlock.EXTENDED, true).getCollisionShape(level, pos); 34 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/tps/ServerTickInformation.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.tps; 2 | 3 | import me.samsuik.sakura.configuration.GlobalConfiguration; 4 | import me.samsuik.sakura.tps.graph.GraphComponents; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.text.TextComponent; 7 | import net.kyori.adventure.text.format.TextColor; 8 | import org.jspecify.annotations.NullMarked; 9 | 10 | @NullMarked 11 | public record ServerTickInformation( 12 | long identifier, 13 | double tps, 14 | double averageTick, 15 | long longestTick, 16 | float targetTickRate, 17 | int chunks, 18 | int entities 19 | ) { 20 | public static final ServerTickInformation UNKNOWN = new ServerTickInformation(0, 0.0, 0.0, 0, 0.0f, 0, 0); 21 | 22 | public TextColor colour() { 23 | final float tpsLoss = (float) this.tps / this.targetTickRate; 24 | return GraphComponents.colour(tpsLoss); 25 | } 26 | 27 | public Component hoverComponent(final TextColor colour) { 28 | final TextComponent.Builder builder = Component.text(); 29 | builder.append(Component.text("TPS: ") 30 | .append(Component.text("%.1f".formatted(this.tps), colour))); 31 | builder.appendNewline(); 32 | builder.append(Component.text("MSPT: ") 33 | .append(Component.text("%.1f".formatted(this.averageTick), colour)) 34 | .append(Component.text("/")) 35 | .append(Component.text(this.longestTick, colour))); 36 | if (GlobalConfiguration.get().messages.tpsShowEntityAndChunkCount) { 37 | builder.appendNewline(); 38 | builder.append(Component.text("Entities: " + this.entities)); 39 | builder.appendNewline(); 40 | builder.append(Component.text("Chunks: " + this.chunks)); 41 | } 42 | return builder.build(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V4_RenameNonStrictMergeLevel.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 4 | import org.jspecify.annotations.NullMarked; 5 | import org.jspecify.annotations.Nullable; 6 | import org.spongepowered.configurate.ConfigurateException; 7 | import org.spongepowered.configurate.ConfigurationNode; 8 | import org.spongepowered.configurate.NodePath; 9 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 10 | import org.spongepowered.configurate.transformation.TransformAction; 11 | 12 | import java.util.Locale; 13 | 14 | import static org.spongepowered.configurate.NodePath.path; 15 | 16 | @NullMarked 17 | public final class V4_RenameNonStrictMergeLevel implements TransformAction { 18 | private static final int VERSION = 4; 19 | private static final String OLD_LEVEL_NAME = "NON_STRICT"; 20 | private static final String NEW_LEVEL_NAME = "LENIENT"; 21 | private static final NodePath MERGE_LEVEL_PATH = path("cannons", "merge-level"); 22 | private static final V4_RenameNonStrictMergeLevel INSTANCE = new V4_RenameNonStrictMergeLevel(); 23 | 24 | private V4_RenameNonStrictMergeLevel() {} 25 | 26 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 27 | builder.addVersion(VERSION, ConfigurationTransformations.transform(MERGE_LEVEL_PATH, INSTANCE)); 28 | } 29 | 30 | @Override 31 | public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { 32 | final String level = value.getString(); 33 | if (level != null && OLD_LEVEL_NAME.equals(level.toUpperCase(Locale.ENGLISH))) { 34 | value.set(NEW_LEVEL_NAME); 35 | } 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V10_DurableMaterialOnlyDamagedByTnt.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import org.jspecify.annotations.NullMarked; 4 | import org.jspecify.annotations.Nullable; 5 | import org.spongepowered.configurate.ConfigurateException; 6 | import org.spongepowered.configurate.ConfigurationNode; 7 | import org.spongepowered.configurate.NodePath; 8 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 9 | import org.spongepowered.configurate.transformation.TransformAction; 10 | 11 | import static org.spongepowered.configurate.NodePath.path; 12 | 13 | @NullMarked 14 | public final class V10_DurableMaterialOnlyDamagedByTnt implements TransformAction { 15 | private static final int VERSION = 10; 16 | private static final V10_DurableMaterialOnlyDamagedByTnt INSTANCE = new V10_DurableMaterialOnlyDamagedByTnt(); 17 | private static final NodePath EXPLOSION_PATH = path("cannons", "explosion"); 18 | private static final NodePath DURABLE_MATERIALS_PATH = EXPLOSION_PATH.plus(path("durable-materials")); 19 | private static final NodePath REQUIRE_TNT_PATH = EXPLOSION_PATH.plus(path("require-tnt-to-damage-durable-materials")); 20 | 21 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 22 | builder.addVersion(VERSION, ConfigurationTransformation.builder() 23 | .addAction(DURABLE_MATERIALS_PATH, INSTANCE) 24 | .addAction(REQUIRE_TNT_PATH, TransformAction.remove()) 25 | .build()); 26 | } 27 | 28 | @Override 29 | public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { 30 | for (final ConfigurationNode child : value.childrenList()) { 31 | child.node(path("only-damaged-by-tnt"), true); 32 | } 33 | 34 | return new Object[0]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0014-Use-maxEntityCollision-limit-for-entity-retrieval.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Thu, 23 Sep 2021 18:50:13 +0100 4 | Subject: [PATCH] Use maxEntityCollision limit for entity retrieval 5 | 6 | 7 | diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java 8 | index 6ee6eacf4e71d5a18aa41877ba6e8b732be83555..018d511c0ebc961bf8eca260e1810f74e085eb72 100644 9 | --- a/net/minecraft/world/entity/LivingEntity.java 10 | +++ b/net/minecraft/world/entity/LivingEntity.java 11 | @@ -3791,7 +3791,17 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin 12 | return; 13 | } 14 | // Paper end - don't run getEntities if we're not going to use its result 15 | - List pushableEntities = this.level().getPushableEntities(this, this.getBoundingBox()); 16 | + // Sakura start - use maxEntityCollision limit for entity retrieval 17 | + //List pushableEntities = this.level().getPushableEntities(this, this.getBoundingBox()); 18 | + final int limit = Math.max(_int, this.level().paperConfig().collisions.maxEntityCollisions); 19 | + final int search = limit * limit; 20 | + final List pushableEntities = new java.util.ArrayList<>(4); 21 | + this.level().getEntities( 22 | + net.minecraft.world.level.entity.EntityTypeTest.forClass(Entity.class), 23 | + this.getBoundingBox(), EntitySelector.pushableBy(this), 24 | + pushableEntities, limit, search 25 | + ); 26 | + // Sakura end - use maxEntityCollision limit for entity retrieval 27 | if (!pushableEntities.isEmpty()) { 28 | if (this.level() instanceof ServerLevel serverLevel) { 29 | // Paper - don't run getEntities if we're not going to use its result; moved up 30 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/configuration/local/ConfigurableKey.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.local; 2 | 3 | import me.samsuik.sakura.explosion.durable.DurableMaterialsContainer.SealedDurableMaterialsContainer; 4 | import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget; 5 | import me.samsuik.sakura.redstone.RedstoneConfiguration; 6 | import org.jspecify.annotations.NullMarked; 7 | import org.jspecify.annotations.Nullable; 8 | 9 | /** 10 | * A key for a configurable value. 11 | */ 12 | @NullMarked 13 | public record ConfigurableKey(Class expectedType) { 14 | public static final ConfigurableKey MECHANICS_TARGET = new ConfigurableKey<>(MinecraftMechanicsTarget.class); 15 | public static final ConfigurableKey DURABLE_MATERIALS = new ConfigurableKey<>(SealedDurableMaterialsContainer.class); 16 | public static final ConfigurableKey REDSTONE_BEHAVIOUR = new ConfigurableKey<>(RedstoneConfiguration.class); 17 | public static final ConfigurableKey CONSISTENT_EXPLOSION_RADIUS = new ConfigurableKey<>(Boolean.class); 18 | public static final ConfigurableKey LAVA_FLOW_SPEED = new ConfigurableKey<>(Integer.class); 19 | 20 | public T validate(@Nullable final Object value) { 21 | final T casted = this.conform(value); 22 | if (casted == null) { 23 | throw new IllegalArgumentException("Value cannot be null for key " + this); 24 | } 25 | return casted; 26 | } 27 | 28 | public @Nullable T conform(@Nullable final Object value) { 29 | if (value == null) { 30 | return null; 31 | } 32 | if (!this.expectedType.isInstance(value)) { 33 | throw new IllegalArgumentException("Expected type " + this.expectedType.getName() + " but got " + value.getClass().getName()); 34 | } 35 | return this.expectedType.cast(value); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/gui/FeatureGui.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.gui; 2 | 3 | import me.samsuik.sakura.player.gui.components.GuiComponent; 4 | import net.kyori.adventure.text.Component; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.inventory.InventoryClickEvent; 7 | import org.bukkit.inventory.Inventory; 8 | import org.jetbrains.annotations.ApiStatus; 9 | import org.jspecify.annotations.NullMarked; 10 | 11 | @NullMarked 12 | public abstract class FeatureGui { 13 | private final int size; 14 | private final Component title; 15 | 16 | public FeatureGui(final int size, final Component title) { 17 | this.size = size; 18 | this.title = title; 19 | } 20 | 21 | protected abstract void fillInventory(final Inventory inventory); 22 | 23 | protected abstract void afterFill(final Player player, final FeatureGuiInventory inventory); 24 | 25 | public final void showTo(final Player bukkitPlayer) { 26 | final FeatureGuiInventory featureInventory = new FeatureGuiInventory(this, this.size, this.title); 27 | this.fillInventory(featureInventory.getInventory()); 28 | this.afterFill(bukkitPlayer, featureInventory); 29 | bukkitPlayer.openInventory(featureInventory.getInventory()); 30 | } 31 | 32 | @ApiStatus.Internal 33 | public static void clickEvent(final InventoryClickEvent event) { 34 | final Inventory clicked = event.getClickedInventory(); 35 | if (clicked != null && clicked.getHolder(false) instanceof FeatureGuiInventory featureInventory) { 36 | event.setCancelled(true); // cancel the event first to let components uncancel the event if desired. 37 | for (final GuiComponent component : featureInventory.getComponents().reversed()) { 38 | if (component.interaction(event, featureInventory)) { 39 | break; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/SugarCaneBlock.java 2 | +++ b/net/minecraft/world/level/block/SugarCaneBlock.java 3 | @@ -48,6 +_,19 @@ 4 | 5 | @Override 6 | protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { 7 | + // Sakura start - use random chance for crop growth 8 | + if (level.sakuraConfig().environment.crops.useRandomChanceToGrow) { 9 | + final int modifier = level.spigotConfig.caneModifier; 10 | + if (random.nextFloat() >= modifier / (100.0f * 16)) { 11 | + return; 12 | + } 13 | + // set crop age to max so it grows right away 14 | + state = state.setValue(SugarCaneBlock.AGE, AGE.max); 15 | + } 16 | + this.ageAndGrow(state, level, pos, random); 17 | + } 18 | + private void ageAndGrow(final BlockState state, final ServerLevel level, final BlockPos pos, final RandomSource random) { 19 | + // Sakura end - use random chance for crop growth 20 | if (level.isEmptyBlock(pos.above())) { 21 | int i = 1; 22 | 23 | @@ -61,6 +_,7 @@ 24 | if (ageValue >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0F * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution 25 | org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos.above(), this.defaultBlockState(), 3); // CraftBukkit 26 | level.setBlock(pos, state.setValue(AGE, 0), Block.UPDATE_NONE); 27 | + // Sakura - use random chance for crop growth; conflict on change 28 | } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0F * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution 29 | level.setBlock(pos, state.setValue(AGE, ageValue + 1), Block.UPDATE_NONE); 30 | } 31 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/CactusBlock.java 2 | +++ b/net/minecraft/world/level/block/CactusBlock.java 3 | @@ -52,6 +_,19 @@ 4 | 5 | @Override 6 | protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { 7 | + // Sakura start - use random chance for crop growth 8 | + if (level.sakuraConfig().environment.crops.useRandomChanceToGrow) { 9 | + final int modifier = level.spigotConfig.cactusModifier; 10 | + if (random.nextFloat() >= modifier / (100.0f * 16)) { 11 | + return; 12 | + } 13 | + // set crop age to max so it grows right away 14 | + state = state.setValue(CactusBlock.AGE, AGE.max); 15 | + } 16 | + this.ageAndGrow(state, level, pos, random); 17 | + } 18 | + private void ageAndGrow(final BlockState state, final ServerLevel level, final BlockPos pos, final RandomSource random) { 19 | + // Sakura end - use random chance for crop growth 20 | BlockPos blockPos = pos.above(); 21 | if (level.isEmptyBlock(blockPos)) { 22 | int i = 1; 23 | @@ -75,6 +_,11 @@ 24 | } 25 | // Paper end 26 | BlockState blockState = state.setValue(AGE, 0); 27 | + // Sakura start - use random chance for crop growth; fix cactus growing next to a block and not breaking 28 | + if (level.sakuraConfig().environment.crops.useRandomChanceToGrow) { 29 | + level.neighborShapeChanged(Direction.UP, blockPos, pos, state, 4, 1); 30 | + } 31 | + // Sakura end - use random chance for crop growth; fix cactus growing next to a block and not breaking 32 | level.setBlock(pos, blockState, Block.UPDATE_NONE); 33 | level.neighborChanged(blockState, blockPos, this, null, false); 34 | } 35 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/LiquidBlock.java 2 | +++ b/net/minecraft/world/level/block/LiquidBlock.java 3 | @@ -145,7 +_,7 @@ 4 | 5 | // Paper start - Configurable speed for water flowing over lava 6 | public int getFlowSpeed(Level level, BlockPos pos) { 7 | - if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) { 8 | + if (this.fluid.getTickDelay(level) != level.paperConfig().environment.waterOverLavaFlowSpeed && net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) { // Sakura - avoid expensive lava search 9 | if ( 10 | isLava(level, pos.north()) || 11 | isLava(level, pos.south()) || 12 | @@ -155,7 +_,7 @@ 13 | return level.paperConfig().environment.waterOverLavaFlowSpeed; 14 | } 15 | } 16 | - return this.fluid.getTickDelay(level); 17 | + return this.fluid.getTickDelay(level, pos); // Sakura - lava flow speed api 18 | } 19 | 20 | private static boolean isLava(Level level, BlockPos pos) { 21 | @@ -195,6 +_,11 @@ 22 | 23 | for (Direction direction : POSSIBLE_FLOW_DIRECTIONS) { 24 | BlockPos blockPos = pos.relative(direction.getOpposite()); 25 | + // Sakura start - configure fluid ticking outside the world border 26 | + if (level.sakuraConfig().environment.disableFluidsFlowingThroughTheWorldBorder && !level.getWorldBorder().isWithinBounds(blockPos)) { 27 | + continue; 28 | + } 29 | + // Sakura end - configure fluid ticking outside the world border 30 | if (level.getFluidState(blockPos).is(FluidTags.WATER)) { 31 | Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; 32 | // CraftBukkit start 33 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/server/level/ServerLevel.java 2 | +++ b/net/minecraft/server/level/ServerLevel.java 3 | @@ -609,7 +_,7 @@ 4 | org.bukkit.generator.BiomeProvider biomeProvider // CraftBukkit 5 | ) { 6 | // CraftBukkit start 7 | - super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.location(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules())), dispatcher); // Paper - create paper world configs & Async-Anti-Xray: Pass executor 8 | + super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.location(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules())), () -> server.sakuraConfigurations.createWorldConfig(me.samsuik.sakura.configuration.SakuraConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.location(), server.registryAccess())), dispatcher); // Sakura - sakura configuration files // Paper - create paper world configs & Async-Anti-Xray: Pass executor 9 | this.levelStorageAccess = levelStorageAccess; 10 | this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getOrCreate(levelStorageAccess.levelDirectory.path().toFile()); 11 | this.levelLoadListener = new net.minecraft.server.level.progress.LoggingLevelLoadListener(false, this); 12 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command; 2 | 3 | import me.samsuik.sakura.command.subcommands.*; 4 | import me.samsuik.sakura.command.subcommands.debug.DebugCommand; 5 | import me.samsuik.sakura.command.subcommands.debug.DebugLocalConfiguration; 6 | import me.samsuik.sakura.command.subcommands.debug.DebugRedstoneCache; 7 | import me.samsuik.sakura.player.visibility.VisibilityTypes; 8 | import net.minecraft.server.MinecraftServer; 9 | import org.bukkit.command.Command; 10 | import org.jspecify.annotations.NullMarked; 11 | 12 | import java.util.HashMap; 13 | import java.util.HashSet; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | @NullMarked 18 | public final class SakuraCommands { 19 | public static final Map COMMANDS = new HashMap<>(); 20 | public static final Set SUB_COMMANDS = new HashSet<>(); 21 | public static final Set DEBUG_COMMANDS = new HashSet<>(); 22 | 23 | static { 24 | COMMANDS.put("config", new ConfigCommand("config")); 25 | COMMANDS.put("tps", new TPSCommand("tps")); 26 | COMMANDS.put("fps", new FPSCommand("fps")); 27 | COMMANDS.put("tntvisibility", new VisualCommand(VisibilityTypes.TNT, "tnttoggle")); 28 | COMMANDS.put("sandvisibility", new VisualCommand(VisibilityTypes.SAND, "sandtoggle")); 29 | COMMANDS.put("mechanic", new MechanicCommand("mechanic")); 30 | SUB_COMMANDS.addAll(COMMANDS.values()); 31 | SUB_COMMANDS.add(new DebugCommand("debug")); 32 | // "sakura" isn't a subcommand 33 | COMMANDS.put("sakura", new SakuraCommand("sakura")); 34 | DEBUG_COMMANDS.add(new DebugRedstoneCache("redstone-cache")); 35 | DEBUG_COMMANDS.add(new DebugLocalConfiguration("local-regions")); 36 | } 37 | 38 | public static void registerCommands(MinecraftServer server) { 39 | COMMANDS.forEach((name, command) -> { 40 | server.server.getCommandMap().register(name, "sakura", command); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/inventory/AbstractContainerMenu.java 2 | +++ b/net/minecraft/world/inventory/AbstractContainerMenu.java 3 | @@ -192,7 +_,7 @@ 4 | 5 | for (int size = this.slots.size(); i < size; i++) { 6 | ItemStack item = this.slots.get(i).getItem(); 7 | - list.add(item.copy()); 8 | + list.add(item.copyForPacket()); // Sakura - modify components sent to the client 9 | this.remoteSlots.get(i).force(item); 10 | } 11 | 12 | @@ -205,7 +_,7 @@ 13 | } 14 | 15 | if (this.synchronizer != null) { 16 | - this.synchronizer.sendInitialData(this, list, carried.copy(), this.remoteDataSlots.toIntArray()); 17 | + this.synchronizer.sendInitialData(this, list, carried.copyForPacket(), this.remoteDataSlots.toIntArray()); // Sakura - modify components sent to the client 18 | this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot 19 | } 20 | } 21 | @@ -239,7 +_,7 @@ 22 | ItemStack carried = this.getCarried(); 23 | this.remoteCarried.force(carried); 24 | if (this.synchronizer != null) { 25 | - this.synchronizer.sendCarriedChange(this, carried.copy()); 26 | + this.synchronizer.sendCarriedChange(this, carried.copyForPacket()); // Sakura - modify components sent to the client 27 | } 28 | } 29 | // CraftBukkit end 30 | @@ -343,7 +_,7 @@ 31 | if (!this.remoteCarried.matches(carried)) { 32 | this.remoteCarried.force(carried); 33 | if (this.synchronizer != null) { 34 | - this.synchronizer.sendCarriedChange(this, carried.copy()); 35 | + this.synchronizer.sendCarriedChange(this, carried.copyForPacket()); // Sakura - modify components sent to the client 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /scripts/apatch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | gitcmd="git -c commit.gpgsign=false" 4 | 5 | noapply=1 6 | isreject=0 7 | if [[ $1 == "--noapplied" ]]; then 8 | noapply=1 9 | shift 10 | fi 11 | 12 | if [ ! -z "$1" ]; then 13 | file="$1" 14 | elif [ -z "$1" ] && [ -f .git/rebase-apply/patch ]; then 15 | file=".git/rebase-apply/patch" 16 | noapply=1 17 | isreject=1 18 | else 19 | echo "Please specify a file" 20 | exit 1 21 | fi 22 | applied=$(echo $file | sed 's/.patch$/-applied\.patch/g') 23 | if [ "$1" == "--reset" ]; then 24 | $gitcmd am --abort 25 | $gitcmd reset --hard 26 | $gitcmd clean -f 27 | exit 0 28 | fi 29 | 30 | 31 | (test "$isreject" != "1" && $gitcmd am -3 $file) || ( 32 | echo "Failures - Wiggling" 33 | $gitcmd reset --hard 34 | $gitcmd clean -f 35 | errors=$($gitcmd apply --rej $file 2>&1) 36 | echo "$errors" >> ~/patch.log 37 | export missingfiles="" 38 | export summaryfail="" 39 | export summarygood="" 40 | for i in $(find . -name \*.rej); do 41 | base=$(echo "$i" | sed 's/.rej//g') 42 | if [ -f "$i" ]; then 43 | sed -e 's/^diff a\/\(.*\) b\/\(.*\)[[:space:]].*rejected.*$/--- \1\n+++ \2/' -i '' $i && wiggle -v -l --replace "$base" "$i" 44 | rm "$base.porig" "$i" 45 | else 46 | echo "No such file: $base" 47 | missingfiles="$missingfiles\n$base" 48 | fi 49 | done 50 | for i in $($gitcmd status --porcelain | awk '{print $2}'); do 51 | filedata=$(cat "$i") 52 | if [ -f "$file" ] && [[ "$filedata" == *"<<<<<"* ]]; then 53 | export summaryfail="$summaryfail\nFAILED TO APPLY: $i" 54 | else 55 | $gitcmd add --force "$i" 56 | export summarygood="$summarygood\nAPPLIED CLEAN: $i" 57 | fi 58 | done 59 | echo -e "$summarygood" 60 | echo -e "$summaryfail" 61 | if [[ "$errors" == *"No such file"* ]]; then 62 | echo "==========================="; 63 | echo " " 64 | echo " MISSING FILES" 65 | echo $(echo "$errors" | grep "No such file") 66 | echo -e "$missingfiles" 67 | echo " " 68 | echo "==========================="; 69 | fi 70 | $gitcmd status 71 | $gitcmd diff 72 | ) 73 | if [[ "$noapply" != "1" ]] && [[ "$file" != *-applied.patch ]]; then 74 | mv "$file" "$applied" 75 | fi 76 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0007-Store-Entity-Data-State.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Wed, 16 Aug 2023 22:34:49 +0100 4 | Subject: [PATCH] Store Entity Data/State 5 | 6 | 7 | diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java 8 | index 1b7019c1564fb9fe8f48c3f0983a2c64ea0aa4a5..b6d65c9ce0fc3c5a27e6de40c3ce26f75bf77426 100644 9 | --- a/net/minecraft/world/entity/Entity.java 10 | +++ b/net/minecraft/world/entity/Entity.java 11 | @@ -551,6 +551,21 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name 12 | return flags; 13 | } 14 | // Sakura end - load chunks on movement 15 | + // Sakura start - store entity data/state 16 | + private me.samsuik.sakura.entity.EntityState entityState = null; 17 | + 18 | + public final void storeEntityState() { 19 | + this.entityState = me.samsuik.sakura.entity.EntityState.of(this); 20 | + } 21 | + 22 | + public final me.samsuik.sakura.entity.EntityState entityState() { 23 | + return this.entityState; 24 | + } 25 | + 26 | + public final boolean compareState(final Entity to) { 27 | + return to.entityState() != null && to.entityState().comparePositionAndMotion(this); 28 | + } 29 | + // Sakura end - store entity data/state 30 | 31 | public Entity(EntityType type, Level level) { 32 | this.type = type; 33 | diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java 34 | index 7cbb9af035092cf2764913c565a3a9398f09e747..c51231f40c31b153ac463e31656584e6c70d5a78 100644 35 | --- a/net/minecraft/world/level/Level.java 36 | +++ b/net/minecraft/world/level/Level.java 37 | @@ -1449,6 +1449,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl 38 | 39 | public void guardEntityTick(Consumer action, T entity) { 40 | try { 41 | + entity.storeEntityState(); // Sakura - store entity data/state 42 | action.accept(entity); 43 | } catch (Throwable var6) { 44 | // Paper start - Prevent block entity and entity crashes 45 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/WebBlock.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/block/WebBlock.java 2 | +++ b/net/minecraft/world/level/block/WebBlock.java 3 | @@ -10,6 +_,7 @@ 4 | import net.minecraft.world.level.block.state.BlockBehaviour; 5 | import net.minecraft.world.level.block.state.BlockState; 6 | import net.minecraft.world.phys.Vec3; 7 | +import net.minecraft.world.phys.shapes.EntityCollisionContext; 8 | 9 | public class WebBlock extends Block { 10 | public static final MapCodec CODEC = simpleCodec(WebBlock::new); 11 | @@ -22,6 +_,31 @@ 12 | public WebBlock(BlockBehaviour.Properties properties) { 13 | super(properties); 14 | } 15 | + 16 | + // Sakura start - configure players colliding with cobwebs 17 | + @Override 18 | + protected final net.minecraft.world.phys.shapes.VoxelShape getCollisionShape( 19 | + final BlockState state, 20 | + final net.minecraft.world.level.BlockGetter blockGetter, 21 | + final BlockPos pos, 22 | + final net.minecraft.world.phys.shapes.CollisionContext context 23 | + ) { 24 | + if (blockGetter instanceof Level level 25 | + && level.sakuraConfig().players.collideWithCobwebs 26 | + && context instanceof EntityCollisionContext entityContext 27 | + && entityContext.getEntity() instanceof net.minecraft.world.entity.player.Player) { 28 | + final BlockPos blockPosAbove = pos.above(); 29 | + final BlockState stateAbove = level.getBlockState(blockPosAbove); 30 | + 31 | + // If the block above is a full block then this cobweb should have a solid collision (for players) 32 | + if (stateAbove.getCollisionShape(level, blockPosAbove, context).moonrise$isFullBlock()) { 33 | + return net.minecraft.world.phys.shapes.Shapes.block(); 34 | + } 35 | + } 36 | + 37 | + return super.getCollisionShape(state, blockGetter, pos, context); 38 | + } 39 | + // Sakura end - configure players colliding with cobwebs 40 | 41 | @Override 42 | protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity, InsideBlockEffectApplier effectApplier, boolean pastEdges) { 43 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V5_CombineLoadChunksOptions.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.transformation.world; 2 | 3 | import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; 4 | import org.jspecify.annotations.NullMarked; 5 | import org.jspecify.annotations.Nullable; 6 | import org.spongepowered.configurate.ConfigurateException; 7 | import org.spongepowered.configurate.ConfigurationNode; 8 | import org.spongepowered.configurate.NodePath; 9 | import org.spongepowered.configurate.transformation.ConfigurationTransformation; 10 | import org.spongepowered.configurate.transformation.TransformAction; 11 | 12 | import java.util.List; 13 | 14 | import static org.spongepowered.configurate.NodePath.path; 15 | 16 | @NullMarked 17 | public final class V5_CombineLoadChunksOptions implements TransformAction { 18 | private static final int VERSION = 5; 19 | private static final List ENTITY_PATHS = List.of("tnt", "sand"); 20 | private static final String OLD_NAME = "loads-chunks"; 21 | private static final String NAME = "load-chunks"; 22 | private static final NodePath CANNONS_PATH = path("cannons"); 23 | private static final V5_CombineLoadChunksOptions INSTANCE = new V5_CombineLoadChunksOptions(); 24 | 25 | private V5_CombineLoadChunksOptions() {} 26 | 27 | public static void apply(final ConfigurationTransformation.VersionedBuilder builder) { 28 | builder.addVersion(VERSION, ConfigurationTransformations.transform(CANNONS_PATH, INSTANCE)); 29 | } 30 | 31 | @Override 32 | public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { 33 | boolean shouldLoadChunks = false; 34 | 35 | for (final String entity : ENTITY_PATHS) { 36 | final NodePath entityPath = NodePath.path(entity, OLD_NAME); 37 | if (value.hasChild(entityPath)) { 38 | final ConfigurationNode node = value.node(entityPath); 39 | shouldLoadChunks |= node.getBoolean(); 40 | node.raw(null); 41 | } 42 | } 43 | 44 | value.node(NAME).set(shouldLoadChunks); 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/configuration/serializer/MinecraftMechanicsTargetSerializer.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.configuration.serializer; 2 | 3 | import me.samsuik.sakura.mechanics.MechanicVersion; 4 | import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget; 5 | import me.samsuik.sakura.mechanics.ServerType; 6 | import org.checkerframework.checker.nullness.qual.Nullable; 7 | import org.jspecify.annotations.NullMarked; 8 | import org.spongepowered.configurate.ConfigurationNode; 9 | import org.spongepowered.configurate.NodePath; 10 | import org.spongepowered.configurate.serialize.SerializationException; 11 | import org.spongepowered.configurate.serialize.TypeSerializer; 12 | 13 | import java.lang.reflect.Type; 14 | 15 | @NullMarked 16 | public final class MinecraftMechanicsTargetSerializer implements TypeSerializer { 17 | private static final NodePath MECHANIC_VERSION = NodePath.path("mechanic-version"); 18 | private static final NodePath SERVER_TYPE = NodePath.path("server-type"); 19 | 20 | @Override 21 | public MinecraftMechanicsTarget deserialize(final Type type, final ConfigurationNode root) throws SerializationException { 22 | final String mechanicVersion = root.node(MECHANIC_VERSION).getString(); 23 | final String serverType = root.node(SERVER_TYPE).getString(); 24 | final MinecraftMechanicsTarget mechanicsTarget = MinecraftMechanicsTarget.fromString("%s+%s".formatted(mechanicVersion, serverType)); 25 | 26 | if (mechanicsTarget == null) { 27 | throw new IllegalArgumentException("Unable to deserialize MinecraftMechanicsTarget (" + mechanicVersion + ", " + serverType + ")"); 28 | } 29 | 30 | return mechanicsTarget; 31 | } 32 | 33 | @Override 34 | public void serialize( 35 | final Type type, 36 | @Nullable MinecraftMechanicsTarget mechanicsTarget, 37 | final ConfigurationNode root 38 | ) throws SerializationException { 39 | if (mechanicsTarget == null) { 40 | mechanicsTarget = MinecraftMechanicsTarget.latest(); 41 | } 42 | 43 | root.node(MECHANIC_VERSION).set(MechanicVersion.name(mechanicsTarget.mechanicVersion())); 44 | root.node(SERVER_TYPE).set(ServerType.name(mechanicsTarget.serverType())); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0018-Allow-explosions-to-destroy-lava.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Sat, 25 Nov 2023 21:14:45 +0000 4 | Subject: [PATCH] Allow explosions to destroy lava 5 | 6 | 7 | diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java 8 | index 1070ad18c1d9441f7fa38a06541e54ccb2ddd63f..c9845e8d486b30c275ebe7585aefb9b1064f33d2 100644 9 | --- a/net/minecraft/world/level/ServerExplosion.java 10 | +++ b/net/minecraft/world/level/ServerExplosion.java 11 | @@ -373,6 +373,11 @@ public class ServerExplosion implements Explosion { 12 | return Optional.of(ZERO_RESISTANCE); 13 | } 14 | // Sakura end - destroy water logged blocks 15 | + // Sakura start - allow explosions to destroy lava 16 | + if (blockState.is(net.minecraft.world.level.block.Blocks.LAVA) && this.level.sakuraConfig().cannons.explosion.explodeLava) { 17 | + return Optional.of(ZERO_RESISTANCE); 18 | + } 19 | + // Sakura end - allow explosions to destroy lava 20 | } 21 | 22 | return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState); 23 | diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java 24 | index d31da50baeb7f47f0e1d81ee3c05023370df8cd3..151ceb365d9ae8f27f3e182ab8150f4e3c9e9110 100644 25 | --- a/net/minecraft/world/level/block/state/BlockBehaviour.java 26 | +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java 27 | @@ -200,7 +200,7 @@ public abstract class BlockBehaviour implements FeatureElement { 28 | state.getDrops(builder).forEach(stack -> dropConsumer.accept(stack, pos)); 29 | } 30 | 31 | - level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL); 32 | + level.setBlock(pos, Blocks.AIR.defaultBlockState(), level.sakuraConfig().cannons.explosion.explodeLava && state.is(Blocks.LAVA) ? Block.UPDATE_CLIENTS : Block.UPDATE_ALL); // Sakura - allow explosions to destroy lava; don't cause block updates when blowing up lava 33 | block.wasExploded(level, pos, explosion); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/mechanics/MinecraftVersionEncoding.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.mechanics; 2 | 3 | /** 4 | * Encode Minecraft versions into a short. 5 | */ 6 | public final class MinecraftVersionEncoding { 7 | private static final int SIGNIFICANT_SHIFT = Short.SIZE - 4; 8 | private static final int MAJOR_SHIFT = SIGNIFICANT_SHIFT - 6; 9 | private static final int MINOR_SHIFT = MAJOR_SHIFT - 6; 10 | 11 | /** 12 | * Encodes a 1.x.y Minecraft version into a short. 13 | * 14 | * @param major the major version (x) 15 | * @param minor the minor version (y) 16 | * @return the encoded version as a short 17 | */ 18 | public static short v1xy(final int major, final int minor) { 19 | return encode(1, major, minor); 20 | } 21 | 22 | /** 23 | * Encodes a Minecraft version into a short. 24 | * 25 | * @param significant the significant version 26 | * @param major the major version 27 | * @param minor the minor version 28 | * @return the encoded version as a short 29 | */ 30 | public static short encode(final int significant, final int major, final int minor) { 31 | short encoded = 0; 32 | encoded |= (short) (significant << SIGNIFICANT_SHIFT); 33 | encoded |= (short) (major << MAJOR_SHIFT); 34 | encoded |= (short) (minor << MINOR_SHIFT); 35 | return encoded; 36 | } 37 | 38 | /** 39 | * Decodes the significant version from an encoded version. 40 | * 41 | * @param version the encoded version 42 | * @return the significant version 43 | */ 44 | public static int significant(final short version) { 45 | return (version >>> SIGNIFICANT_SHIFT) & 15; 46 | } 47 | 48 | /** 49 | * Decodes the major version from an encoded version. 50 | * 51 | * @param version the encoded version 52 | * @return the major version 53 | */ 54 | public static int major(final short version) { 55 | return (version >>> MAJOR_SHIFT) & 63; 56 | } 57 | 58 | /** 59 | * Decodes the minor version from an encoded version. 60 | * 61 | * @param version the encoded version 62 | * @return the minor version 63 | */ 64 | public static int minor(final short version) { 65 | return (version >>> MINOR_SHIFT) & 63; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/tps/graph/ComponentCanvas.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.tps.graph; 2 | 3 | import com.google.common.base.Preconditions; 4 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.text.JoinConfiguration; 7 | import org.jspecify.annotations.NullMarked; 8 | 9 | import java.util.List; 10 | 11 | @NullMarked 12 | public final class ComponentCanvas { 13 | private final int width; 14 | private final int height; 15 | private final Component[][] components; 16 | 17 | public ComponentCanvas(final int width, final int height) { 18 | this.width = width; 19 | this.height = height; 20 | // [x, y] is flipped as it makes converting the components into a list easier 21 | this.components = new Component[height][width]; 22 | } 23 | 24 | public void flip() { 25 | for (int y = 0; y < this.height; ++y) { 26 | if (y >= this.height / 2) { 27 | final Component[] row = this.components[y]; 28 | int relocatingRow = this.height - 1 - y; 29 | this.components[y] = this.components[relocatingRow]; 30 | this.components[relocatingRow] = row; 31 | } 32 | } 33 | } 34 | 35 | public void fill(final Component component) { 36 | for (int x = 0; x < this.width; ++x) { 37 | for (int y = 0; y < this.height; ++y) { 38 | this.set(x, y, component); 39 | } 40 | } 41 | } 42 | 43 | public Component get(final int x, final int y) { 44 | final Component component = this.components[y][x]; 45 | return Preconditions.checkNotNull(component, "missing component at x:{} y:{}", x, y); 46 | } 47 | 48 | public void set(final int x, final int y, final Component component) { 49 | this.components[y][x] = component; 50 | } 51 | 52 | public BuiltComponentCanvas build() { 53 | return new BuiltComponentCanvas(this.joinComponents()); 54 | } 55 | 56 | private List joinComponents() { 57 | final List componentList = new ObjectArrayList<>(this.height); 58 | for (final Component[] row : this.components) { 59 | componentList.add(Component.join(JoinConfiguration.noSeparators(), row)); 60 | } 61 | return componentList; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sakura-api/src/main/java/me/samsuik/sakura/mechanics/MechanicVersion.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.mechanics; 2 | 3 | /** 4 | * All post-1.8 Minecraft versions with changes to cannon mechanics. 5 | *

6 | * Versions are encoded as shorts see {@link MinecraftVersionEncoding}. 7 | */ 8 | public final class MechanicVersion { 9 | public static final short LATEST = Short.MAX_VALUE; 10 | public static final short LEGACY = Short.MIN_VALUE; 11 | public static final short v1_8_2 = MinecraftVersionEncoding.v1xy(8, 2); 12 | public static final short v1_9 = MinecraftVersionEncoding.v1xy(9, 0); 13 | public static final short v1_10 = MinecraftVersionEncoding.v1xy(10, 0); 14 | public static final short v1_11 = MinecraftVersionEncoding.v1xy(11, 0); 15 | public static final short v1_12 = MinecraftVersionEncoding.v1xy(12, 0); 16 | public static final short v1_13 = MinecraftVersionEncoding.v1xy(13, 0); 17 | public static final short v1_14 = MinecraftVersionEncoding.v1xy(14, 0); 18 | public static final short v1_16 = MinecraftVersionEncoding.v1xy(16, 0); 19 | public static final short v1_17 = MinecraftVersionEncoding.v1xy(17, 0); 20 | public static final short v1_18_2 = MinecraftVersionEncoding.v1xy(18, 2); 21 | public static final short v1_19_3 = MinecraftVersionEncoding.v1xy(19, 3); 22 | public static final short v1_20 = MinecraftVersionEncoding.v1xy(20, 0); 23 | public static final short v1_20_2 = MinecraftVersionEncoding.v1xy(20, 2); 24 | public static final short v1_21_2 = MinecraftVersionEncoding.v1xy(21, 2); 25 | public static final short v1_21_5 = MinecraftVersionEncoding.v1xy(21, 5); 26 | public static final short v1_21_6 = MinecraftVersionEncoding.v1xy(21, 6); 27 | public static final short v1_21_9 = MinecraftVersionEncoding.v1xy(21, 9); 28 | public static final short v1_21_10 = MinecraftVersionEncoding.v1xy(21, 10); 29 | 30 | public static String name(final short version) { 31 | if (version == LATEST) { 32 | return "latest"; 33 | } else if (version == LEGACY) { 34 | return "legacy"; 35 | } 36 | 37 | final int significant = MinecraftVersionEncoding.significant(version); 38 | final int major = MinecraftVersionEncoding.major(version); 39 | final int minor = MinecraftVersionEncoding.minor(version); 40 | return String.format("%d.%d.%d", significant, major, minor); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sakura-server/paper-patches/features/0001-Client-Visibility-Settings.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Tue, 21 Sep 2021 23:54:25 +0100 4 | Subject: [PATCH] Client Visibility Settings 5 | 6 | 7 | diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java 8 | index e8bd5c63c9d7ea9f0ba847b897ebb6072f40a4dc..7251ea305abb977d66d8c37a0094cdbf4e0d74cf 100644 9 | --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java 10 | +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java 11 | @@ -2174,6 +2174,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa 12 | handle.expToDrop = data.getIntOr("expToDrop", 0); 13 | handle.keepLevel = data.getBooleanOr("keepLevel", false); 14 | }); 15 | + // Sakura start - client visibility settings; load from nbt 16 | + input.child("sakura").ifPresent(sakura -> { 17 | + this.getHandle().visibilitySettings.loadData(sakura); 18 | + }); 19 | + // Sakura end - client visibility settings; load from nbt 20 | } 21 | 22 | public void setExtraData(ValueOutput output) { 23 | @@ -2195,6 +2200,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa 24 | paper.putLong("LastLogin", handle.loginTime); 25 | paper.putLong("LastSeen", System.currentTimeMillis()); 26 | // Paper end 27 | + // Sakura start - client visibility settings; save to nbt 28 | + final ValueOutput sakura = output.child("sakura"); 29 | + this.getHandle().visibilitySettings.saveData(sakura); 30 | + // Sakura end - client visibility settings; save to nbt 31 | } 32 | 33 | @Override 34 | @@ -2762,6 +2771,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa 35 | return this.getHandle().allowsListing(); 36 | } 37 | 38 | + // Sakura start - client visibility settings; api 39 | + @Override 40 | + public final me.samsuik.sakura.player.visibility.VisibilitySettings getVisibility() { 41 | + return this.getHandle().visibilitySettings; 42 | + } 43 | + // Sakura end - client visibility settings; api 44 | + 45 | // Paper start 46 | @Override 47 | public net.kyori.adventure.text.Component displayName() { 48 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/mechanics/FallingBlockBehaviour.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.mechanics; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.world.entity.item.FallingBlockEntity; 6 | import net.minecraft.world.level.Level; 7 | import net.minecraft.world.level.block.Block; 8 | import net.minecraft.world.level.block.Blocks; 9 | import net.minecraft.world.level.block.FallingBlock; 10 | import net.minecraft.world.level.block.state.BlockState; 11 | import org.jspecify.annotations.NullMarked; 12 | 13 | @NullMarked 14 | public final class FallingBlockBehaviour { 15 | public static boolean isAbleToStackOnBlock(final FallingBlockEntity fallingBlock, final MinecraftMechanicsTarget mechanicsTarget) { 16 | if (!mechanicsTarget.between(me.samsuik.sakura.mechanics.MechanicVersion.v1_9, me.samsuik.sakura.mechanics.MechanicVersion.v1_14)) { 17 | return true; 18 | } 19 | // This is patched by default on Paper. 20 | if (mechanicsTarget.isServerType(me.samsuik.sakura.mechanics.ServerType.PAPER)) { 21 | return true; 22 | } 23 | // todo: Entity#getOnPos might be a good alternative to this 24 | final BlockPos blockPos = BlockPos.containing(fallingBlock.getX(), fallingBlock.getY() - 0.001f, fallingBlock.getZ()); 25 | final BlockState state = fallingBlock.level().getBlockState(blockPos); 26 | return !FallingBlock.isFree(state); 27 | } 28 | 29 | public static void removeBlockOnFall(final FallingBlockEntity fallingBlock, final Block block) { 30 | final Level level = fallingBlock.level(); 31 | final BlockPos blockPos = fallingBlock.blockPosition(); 32 | final BlockState state = level.getBlockState(blockPos); 33 | 34 | // todo: Do we need to call the event here? This event is already called in the fall method that spawns the falling block entity. 35 | if (state.is(block) && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(fallingBlock, blockPos, Blocks.AIR.defaultBlockState())) { 36 | level.removeBlock(blockPos, false); 37 | } else { 38 | if (state.is(block)) { 39 | ((ServerLevel) level).getChunkSource().blockChanged(blockPos); 40 | } 41 | fallingBlock.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sakura-api/build.gradle.kts.patch: -------------------------------------------------------------------------------- 1 | --- a/paper-api/build.gradle.kts 2 | +++ b/paper-api/build.gradle.kts 3 | @@ -67,6 +_,7 @@ 4 | api("org.apache.maven:maven-resolver-provider:3.9.6") // make API dependency for Paper Plugins 5 | implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") 6 | implementation("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") 7 | + compileOnly("org.spongepowered:configurate-yaml:4.2.0") 8 | 9 | // Annotations - Slowly migrate to jspecify 10 | val annotations = "org.jetbrains:annotations:$annotationsVersion" 11 | @@ -89,7 +_,7 @@ 12 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 13 | } 14 | 15 | -val generatedDir: java.nio.file.Path = layout.projectDirectory.dir("src/generated/java").asFile.toPath() 16 | +val generatedDir: java.nio.file.Path = rootProject.layout.projectDirectory.dir("paper-api/src/generated/java").asFile.toPath() 17 | idea { 18 | module { 19 | generatedSourceDirs.add(generatedDir.toFile()) 20 | @@ -99,6 +_,18 @@ 21 | main { 22 | java { 23 | srcDir(generatedDir) 24 | + srcDir(file("../paper-api/src/main/java")) 25 | + } 26 | + resources { 27 | + srcDir(file("../paper-api/src/main/resources")) 28 | + } 29 | + } 30 | + test { 31 | + java { 32 | + srcDir(file("../paper-api/src/test/java")) 33 | + } 34 | + resources { 35 | + srcDir(file("../paper-api/src/test/resources")) 36 | } 37 | } 38 | } 39 | @@ -165,7 +_,7 @@ 40 | 41 | tasks.withType().configureEach { 42 | val options = options as StandardJavadocDocletOptions 43 | - options.overview = "src/main/javadoc/overview.html" 44 | + options.overview = "../paper-api/src/main/javadoc/overview.html" 45 | options.use() 46 | options.isDocFilesSubDirs = true 47 | options.links( 48 | @@ -198,11 +_,11 @@ 49 | } 50 | 51 | // workaround for https://github.com/gradle/gradle/issues/4046 52 | - inputs.dir("src/main/javadoc").withPropertyName("javadoc-sourceset") 53 | + inputs.dir("../paper-api/src/main/javadoc").withPropertyName("javadoc-sourceset") 54 | val fsOps = services.fileSystemOperations 55 | doLast { 56 | fsOps.copy { 57 | - from("src/main/javadoc") { 58 | + from("../paper-api/src/main/javadoc") { 59 | include("**/doc-files/**") 60 | } 61 | into("build/docs/javadoc") 62 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/tps/graph/GraphComponents.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.tps.graph; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | import net.kyori.adventure.text.format.Style; 6 | import net.kyori.adventure.text.format.TextColor; 7 | import net.kyori.adventure.text.format.TextDecoration; 8 | import org.jspecify.annotations.NullMarked; 9 | 10 | import java.util.List; 11 | 12 | @NullMarked 13 | public final class GraphComponents { 14 | private static final Style STRIKE_THROUGH_STYLE = Style.style(TextDecoration.STRIKETHROUGH); 15 | private static final Style REMOVE_STRIKE_THROUGH_STYLE = Style.style(TextDecoration.STRIKETHROUGH.withState(false)); 16 | 17 | public static final Component BACKGROUND = Component.text("::"); 18 | public static final Component HORIZONTAL_LINE = Component.text(" ", STRIKE_THROUGH_STYLE); 19 | public static final Component VERTICAL_LINE = Component.text("||"); 20 | public static final Component TOP_DOTTED_LINE = Component.text("''"); 21 | public static final Component BOTTOM_DOTTED_LINE = Component.text(".."); 22 | public static final Component BL_TO_TR = Component.text(".", STRIKE_THROUGH_STYLE).append(Component.text("'", REMOVE_STRIKE_THROUGH_STYLE)); 23 | public static final Component TL_TO_BR = Component.text("'").append(Component.text(".", STRIKE_THROUGH_STYLE)); 24 | public static final Component CONE_TOP_LEFT = Component.text(".!"); 25 | public static final Component CONE_TOP_RIGHT = Component.text("!."); 26 | public static final Component CONE_BOTTOM_LEFT = Component.text("'!"); 27 | public static final Component CONE_BOTTOM_RIGHT = Component.text("!'"); 28 | 29 | private static final List COLOURS = List.of( 30 | NamedTextColor.GREEN, NamedTextColor.YELLOW, NamedTextColor.GOLD, 31 | NamedTextColor.RED, NamedTextColor.DARK_GRAY, TextColor.color(40, 40, 40) 32 | ); 33 | 34 | public static TextColor colour(final float num) { 35 | final float segment = 1.0f / COLOURS.size(); 36 | final float a = (1.0f - num) / segment; 37 | final float t = a % 1.0f; 38 | final int startIndex = Math.clamp((int) a, 0, COLOURS.size() - 2); 39 | final int endIndex = startIndex + 1; 40 | final TextColor startColour = COLOURS.get(startIndex); 41 | final TextColor endColour = COLOURS.get(endIndex); 42 | return TextColor.lerp(t, startColour, endColour); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/features/0010-Optimise-explosions-in-protected-regions.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: Samsuik 3 | Date: Fri, 3 May 2024 15:18:58 +0100 4 | Subject: [PATCH] Optimise explosions in protected regions 5 | 6 | 7 | diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java 8 | index 39a4f8ffb7c0e61d94c4384c4a3f77550fefe16f..17aaa7cd99f9a0f1d1c31c72bea2973eeb71e600 100644 9 | --- a/net/minecraft/world/level/ServerExplosion.java 10 | +++ b/net/minecraft/world/level/ServerExplosion.java 11 | @@ -311,6 +311,22 @@ public class ServerExplosion implements Explosion { 12 | return (float)missedRays / (float)totalRays; 13 | } 14 | // Paper end - collisions optimisations 15 | + // Sakura start - optimise explosion protected regions 16 | + protected final boolean isRegionUnprotected() { 17 | + // optimisation: We check if a plugin has cancelled the event or cleared the blockList. 18 | + // It tells us if the result was thrown away, so we can avoid the block searching logic. 19 | + // As a side effect the event is called twice which may interfere with some plugins. 20 | + if (this.source != null && this.level.sakuraConfig().cannons.explosion.optimiseProtectedRegions) { 21 | + final Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z); 22 | + final List blocks = new ObjectArrayList<>(1); 23 | + blocks.add(location.getBlock()); 24 | + final org.bukkit.event.entity.EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this.source, blocks, 0.0f, this.blockInteraction); 25 | + return !event.isCancelled() && !event.blockList().isEmpty(); 26 | + } 27 | + 28 | + return true; 29 | + } 30 | + // Sakura end - optimise explosion protected regions 31 | 32 | public ServerExplosion( 33 | ServerLevel level, 34 | @@ -418,6 +434,11 @@ public class ServerExplosion implements Explosion { 35 | return ret; 36 | } 37 | // Sakura end - optimise paper explosions 38 | + // Sakura start - optimise protected explosions 39 | + if (!this.isRegionUnprotected()) { 40 | + return ret; 41 | + } 42 | + // Sakura end - optimise protected explosions 43 | 44 | // only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of 45 | // a 16x16x16 cube 46 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.utils.collections; 2 | 3 | import ca.spottedleaf.moonrise.common.list.ReferenceList; 4 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 5 | import it.unimi.dsi.fastutil.objects.AbstractObjectCollection; 6 | import it.unimi.dsi.fastutil.objects.ObjectCollection; 7 | import it.unimi.dsi.fastutil.objects.ObjectIterator; 8 | import net.minecraft.server.level.ChunkMap; 9 | import org.jspecify.annotations.NullMarked; 10 | 11 | import java.util.Iterator; 12 | 13 | @NullMarked 14 | public final class TrackedEntityChunkMap extends Int2ObjectOpenHashMap { 15 | private final ReferenceList trackedEntityList = new ReferenceList<>(); 16 | 17 | @Override 18 | public ChunkMap.TrackedEntity put(final int index, final ChunkMap.TrackedEntity trackedEntity) { 19 | final ChunkMap.TrackedEntity tracked = super.put(index, trackedEntity); 20 | //noinspection ConstantValue 21 | if (tracked != null) { 22 | this.trackedEntityList.remove(trackedEntity); 23 | } 24 | this.trackedEntityList.add(trackedEntity); 25 | return tracked; 26 | } 27 | 28 | @Override 29 | public ChunkMap.TrackedEntity remove(final int index) { 30 | final ChunkMap.TrackedEntity tracked = super.remove(index); 31 | this.trackedEntityList.remove(tracked); 32 | return tracked; 33 | } 34 | 35 | @Override 36 | public ObjectCollection values() { 37 | return new AbstractObjectCollection<>() { 38 | @Override 39 | public ObjectIterator iterator() { 40 | return new TrackedEntityIterator(); 41 | } 42 | 43 | @Override 44 | public int size() { 45 | return TrackedEntityChunkMap.this.size(); 46 | } 47 | }; 48 | } 49 | 50 | private final class TrackedEntityIterator implements ObjectIterator { 51 | private final Iterator backingItr = TrackedEntityChunkMap.this.trackedEntityList.iterator(); 52 | 53 | @Override 54 | public boolean hasNext() { 55 | return this.backingItr.hasNext(); 56 | } 57 | 58 | @Override 59 | public ChunkMap.TrackedEntity next() { 60 | return this.backingItr.next(); 61 | } 62 | 63 | @Override 64 | public void remove() { 65 | this.backingItr.remove(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugLocalConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command.subcommands.debug; 2 | 3 | import me.samsuik.sakura.command.PlayerOnlySubCommand; 4 | import me.samsuik.sakura.configuration.local.ConfigurationContainer; 5 | import me.samsuik.sakura.configuration.local.LocalConfigurationAccessor; 6 | import org.bukkit.Location; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.util.BoundingBox; 9 | import org.jspecify.annotations.NullMarked; 10 | 11 | import java.util.List; 12 | 13 | @NullMarked 14 | public final class DebugLocalConfiguration extends PlayerOnlySubCommand { 15 | private static final int DEFAULT_REGION_SIZE = 16; 16 | 17 | public DebugLocalConfiguration(final String name) { 18 | super(name); 19 | } 20 | 21 | @Override 22 | public void execute(final Player player, final String[] args) { 23 | final Location location = player.getLocation(); 24 | final LocalConfigurationAccessor localConfigurationAccessor = location.getWorld().localConfig(); 25 | final BoundingBox boundingBox = localConfigurationAccessor.getAreas(location).stream() 26 | .findAny() 27 | .orElse(null); 28 | 29 | if (boundingBox != null) { 30 | player.sendRichMessage("You are currently inside a area with a set local-config."); 31 | player.sendRichMessage(" - %.0f %.0f %.0f".formatted(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ())); 32 | player.sendRichMessage(" - %.0f %.0f %.0f".formatted(boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxX())); 33 | } 34 | 35 | if (args.length == 0) { 36 | return; 37 | } 38 | 39 | if ("delete".equalsIgnoreCase(args[0]) && boundingBox != null) { 40 | localConfigurationAccessor.remove(boundingBox); 41 | player.sendRichMessage("Removed area"); 42 | } 43 | 44 | if ("create".equalsIgnoreCase(args[0]) && args.length > 1) { 45 | final int size = parseInt(args, 1).orElse(DEFAULT_REGION_SIZE); 46 | final BoundingBox area = BoundingBox.of(location, size, size, size); 47 | localConfigurationAccessor.set(area, ConfigurationContainer.sealedContainer()); 48 | player.sendRichMessage("Created a new area with size " + size); 49 | } 50 | } 51 | 52 | @Override 53 | public void tabComplete(final List completions, final String[] args) throws IllegalArgumentException { 54 | completions.addAll(List.of("create", "delete")); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugRedstoneCache.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command.subcommands.debug; 2 | 3 | import me.samsuik.sakura.command.PlayerOnlySubCommand; 4 | import me.samsuik.sakura.redstone.cache.RedstoneNetwork; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.level.Level; 8 | import org.bukkit.DyeColor; 9 | import org.bukkit.Location; 10 | import org.bukkit.Material; 11 | import org.bukkit.craftbukkit.entity.CraftPlayer; 12 | import org.bukkit.craftbukkit.util.CraftLocation; 13 | import org.bukkit.entity.Player; 14 | import org.jspecify.annotations.NullMarked; 15 | 16 | import java.util.HashSet; 17 | import java.util.Set; 18 | import java.util.concurrent.ThreadLocalRandom; 19 | 20 | @NullMarked 21 | public final class DebugRedstoneCache extends PlayerOnlySubCommand { 22 | public DebugRedstoneCache(final String name) { 23 | super(name); 24 | } 25 | 26 | @Override 27 | public void execute(final Player player, final String[] args) { 28 | final ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); 29 | final Level level = nmsPlayer.level(); 30 | final Set redstoneWires = new HashSet<>(); 31 | 32 | // Display randomly coloured wool blocks in place of redstone wires in a network. 33 | for (final RedstoneNetwork network : level.redstoneWireCache.getNetworkCache().values()) { 34 | final byte randomColour = (byte) ThreadLocalRandom.current().nextInt(16); 35 | final DyeColor dyeColour = DyeColor.getByWoolData(randomColour); 36 | final Material material = Material.matchMaterial(dyeColour.name() + "_WOOL"); 37 | 38 | if (!network.isRegistered()) { 39 | continue; 40 | } 41 | 42 | for (final BlockPos pos : network.getWirePositions()) { 43 | final Location location = CraftLocation.toBukkit(pos, level); 44 | if (player.getLocation().distance(location) >= 64.0) { 45 | continue; 46 | } 47 | player.sendBlockChange(location, material.createBlockData()); 48 | redstoneWires.add(location); 49 | } 50 | } 51 | 52 | player.sendRichMessage("Displaying %dx cached redstone wires".formatted(redstoneWires.size())); 53 | 54 | level.levelTickScheduler.runTaskLater(() -> { 55 | for (final Location loc : redstoneWires) { 56 | player.sendBlockChange(loc, loc.getBlock().getBlockData()); 57 | } 58 | }, 1200); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/player/visibility/PlayerVisibilitySettings.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.player.visibility; 2 | 3 | import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; 4 | import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; 5 | import net.minecraft.world.level.storage.ValueInput; 6 | import net.minecraft.world.level.storage.ValueOutput; 7 | import org.jspecify.annotations.NullMarked; 8 | 9 | @NullMarked 10 | public final class PlayerVisibilitySettings implements VisibilitySettings { 11 | private static final String VISIBILITY_SETTINGS_TAG = "client_visibility_settings"; 12 | private final Reference2ObjectMap visibilityStates = new Reference2ObjectOpenHashMap<>(); 13 | 14 | @Override 15 | public VisibilityState get(final VisibilityType type) { 16 | final VisibilityState state = this.visibilityStates.get(type); 17 | //noinspection ConstantValue 18 | return state != null ? state : type.getDefault(); 19 | } 20 | 21 | @Override 22 | public VisibilityState set(final VisibilityType type, final VisibilityState state) { 23 | if (type.isDefault(state)) { 24 | this.visibilityStates.remove(type); 25 | } else { 26 | this.visibilityStates.put(type, state); 27 | } 28 | return state; 29 | } 30 | 31 | @Override 32 | public VisibilityState currentState() { 33 | final int modifiedCount = this.visibilityStates.size(); 34 | if (modifiedCount == 0) { 35 | return VisibilityState.ON; 36 | } else if (modifiedCount != VisibilityTypes.types().size()) { 37 | return VisibilityState.MODIFIED; 38 | } else { 39 | return VisibilityState.OFF; 40 | } 41 | } 42 | 43 | @Override 44 | public boolean playerModified() { 45 | return !this.visibilityStates.isEmpty(); 46 | } 47 | 48 | public void loadData(final ValueInput input) { 49 | input.child(VISIBILITY_SETTINGS_TAG).ifPresent(settings -> { 50 | for (final VisibilityType type : VisibilityTypes.types()) { 51 | final String typeKey = type.key(); 52 | final String stateName = settings.getStringOr(typeKey, type.getDefault().name()); 53 | this.set(type, VisibilityState.valueOf(stateName)); 54 | } 55 | }); 56 | } 57 | 58 | public void saveData(final ValueOutput output) { 59 | final ValueOutput settings = output.child(VISIBILITY_SETTINGS_TAG); 60 | this.visibilityStates.forEach((type, state) -> settings.putString(type.key(), state.name())); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.command; 2 | 3 | import org.bukkit.command.Command; 4 | import org.bukkit.command.CommandSender; 5 | import org.jspecify.annotations.NullMarked; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.function.Function; 11 | 12 | @NullMarked 13 | public abstract class BaseSubCommand extends Command { 14 | public BaseSubCommand(final String name) { 15 | super(name); 16 | this.description = "Sakura Command " + name; 17 | this.setPermission("bukkit.command." + name); 18 | } 19 | 20 | public abstract void execute(final CommandSender sender, final String[] args); 21 | 22 | public void tabComplete(final List completions, final String[] args) throws IllegalArgumentException {} 23 | 24 | @Override 25 | @Deprecated 26 | public final boolean execute(final CommandSender sender, final String label, final String[] args) { 27 | if (this.testPermission(sender)) { 28 | this.execute(sender, args); 29 | } 30 | 31 | return true; 32 | } 33 | 34 | @Override 35 | public List tabComplete(final CommandSender sender, final String alias, final String[] args) throws IllegalArgumentException { 36 | final List completions = new ArrayList<>(0); 37 | 38 | if (this.testPermissionSilent(sender)) { 39 | this.tabComplete(completions, args); 40 | } 41 | 42 | return completions; 43 | } 44 | 45 | protected final Optional parseInt(final String[] args, final int index) { 46 | return this.parse(args, index, Integer::parseInt); 47 | } 48 | 49 | protected final Optional parseLong(final String[] args, final int index) { 50 | return this.parse(args, index, Long::parseLong); 51 | } 52 | 53 | protected final Optional parseFloat(final String[] args, final int index) { 54 | return this.parse(args, index, Float::parseFloat); 55 | } 56 | 57 | protected final Optional parseDouble(final String[] args, final int index) { 58 | return this.parse(args, index, Double::parseDouble); 59 | } 60 | 61 | protected final Optional parse(final String[] args, final int index, final Function parseFunction) { 62 | try { 63 | final String toParse = args[index]; 64 | return Optional.of(parseFunction.apply(toParse)); 65 | } catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) { 66 | return Optional.empty(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/tps/graph/TPSGraph.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.tps.graph; 2 | 3 | import com.google.common.base.Preconditions; 4 | import me.samsuik.sakura.tps.ServerTickInformation; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.text.event.HoverEvent; 7 | import net.kyori.adventure.text.format.NamedTextColor; 8 | import net.kyori.adventure.text.format.TextColor; 9 | import net.minecraft.util.Mth; 10 | import org.jspecify.annotations.NullMarked; 11 | 12 | import java.util.List; 13 | 14 | @NullMarked 15 | public abstract class TPSGraph { 16 | protected final List tickInformation; 17 | protected final int width; 18 | protected final int height; 19 | protected final double scale; 20 | 21 | public TPSGraph(final int width, final int height, final double scale, final List tickInformation) { 22 | Preconditions.checkArgument(tickInformation.size() == width); 23 | this.width = width; 24 | this.height = height; 25 | this.scale = scale; 26 | this.tickInformation = tickInformation; 27 | } 28 | 29 | public abstract BuiltComponentCanvas plot(); 30 | 31 | protected final int rowFromColumn(final int x) { 32 | final int clamped = Math.clamp(x, 0, this.width - 1); 33 | final ServerTickInformation tickInformation = this.tickInformation.get(clamped); 34 | return this.rowFromTPS(tickInformation.tps()); 35 | } 36 | 37 | protected final int rowFromTPS(final double tps) { 38 | final int row = Mth.floor((tps / 3) * this.scale); 39 | return Mth.clamp(row, 0, this.height - 1); 40 | } 41 | 42 | protected final void addColourAndHoverInformation(final ComponentCanvas canvas) { 43 | for (int x = 0; x < this.width; ++x) { 44 | final ServerTickInformation tickInformation = this.tickInformation.get(x); 45 | final TextColor colourFromTPS = tickInformation.colour(); 46 | final Component hoverComponent = tickInformation.hoverComponent(colourFromTPS); 47 | final HoverEvent hoverEvent = HoverEvent.showText(hoverComponent); 48 | 49 | for (int y = 0; y < this.height; ++y) { 50 | Component component = canvas.get(x, y); 51 | if (component == GraphComponents.BACKGROUND) { 52 | component = component.color(NamedTextColor.BLACK); 53 | } else { 54 | component = component.color(colourFromTPS); 55 | } 56 | canvas.set(x, y, component.hoverEvent(hoverEvent)); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sakura-server/src/main/java/me/samsuik/sakura/utils/SimpleBlockPosIterator.java: -------------------------------------------------------------------------------- 1 | package me.samsuik.sakura.utils; 2 | 3 | import com.google.common.collect.AbstractIterator; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.BlockPos.MutableBlockPos; 6 | import net.minecraft.util.Mth; 7 | import net.minecraft.world.phys.AABB; 8 | import net.minecraft.world.phys.Vec3; 9 | import org.jspecify.annotations.NullMarked; 10 | import org.jspecify.annotations.Nullable; 11 | 12 | @NullMarked 13 | public final class SimpleBlockPosIterator extends AbstractIterator { 14 | private final int startX; 15 | private final int startY; 16 | private final int startZ; 17 | private final int endX; 18 | private final int endY; 19 | private final int endZ; 20 | private @Nullable MutableBlockPos pos = null; 21 | 22 | public static Iterable iterable(final AABB bounds) { 23 | return () -> new SimpleBlockPosIterator(bounds); 24 | } 25 | 26 | public static Iterable traverseBoundsInDirection(final Vec3 direction, final AABB bounds, final double maxDistance) { 27 | final double scaledDistance = Math.min(maxDistance / direction.length(), 1.0); 28 | final AABB previousBounds = bounds.move(direction.scale(-1.0)); 29 | final AABB boundsToSearch = previousBounds.expandTowards(direction.scale(scaledDistance)); 30 | return SimpleBlockPosIterator.iterable(boundsToSearch); 31 | } 32 | 33 | public SimpleBlockPosIterator(final AABB bounds) { 34 | this.startX = Mth.floor(bounds.minX); 35 | this.startY = Mth.floor(bounds.minY); 36 | this.startZ = Mth.floor(bounds.minZ); 37 | this.endX = Mth.floor(bounds.maxX); 38 | this.endY = Mth.floor(bounds.maxY); 39 | this.endZ = Mth.floor(bounds.maxZ); 40 | } 41 | 42 | @Override 43 | protected BlockPos computeNext() { 44 | final MutableBlockPos pos = this.pos; 45 | if (pos == null) { 46 | return this.pos = new MutableBlockPos(this.startX, this.startY, this.startZ); 47 | } else { 48 | int x = pos.getX(); 49 | int y = pos.getY(); 50 | int z = pos.getZ(); 51 | 52 | if (y < this.endY) { 53 | y += 1; 54 | } else if (x < this.endX) { 55 | x += 1; 56 | y = this.startY; 57 | } else if (z < this.endZ) { 58 | z += 1; 59 | x = this.startX; 60 | y = this.startY; 61 | } else { 62 | return this.endOfData(); 63 | } 64 | 65 | pos.set(x, y, z); 66 | return pos; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch: -------------------------------------------------------------------------------- 1 | --- a/net/minecraft/world/level/ServerExplosion.java 2 | +++ b/net/minecraft/world/level/ServerExplosion.java 3 | @@ -62,6 +_,7 @@ 4 | public float yield; 5 | // CraftBukkit end 6 | public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source 7 | + private final boolean consistentExplosionRadius; // Sakura - consistent explosion radius 8 | // Paper start - collisions optimisations 9 | private static final double[] CACHED_RAYS; 10 | static { 11 | @@ -328,6 +_,7 @@ 12 | this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; 13 | this.yield = Double.isFinite(this.yield) ? this.yield : 0; // Paper - Don't allow infinite default yields 14 | // Paper end - add yield 15 | + this.consistentExplosionRadius = level.localConfig().at(this.center).consistentExplosionRadius; // Sakura - consistent explosion radius 16 | } 17 | 18 | private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { 19 | @@ -416,8 +_,7 @@ 20 | final double incZ = CACHED_RAYS[ray + 2]; 21 | 22 | ray += 3; 23 | - 24 | - float power = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F); 25 | + float power = this.radius * (0.7F + (this.consistentExplosionRadius ? 0.7F : this.level.random.nextFloat()) * 0.6F); // Sakura - consistent explosion radius 26 | 27 | do { 28 | final int blockX = Mth.floor(currX); 29 | @@ -610,7 +_,20 @@ 30 | .onExplosionHit(this.level, blockPos, this, (itemStack, blockPos1) -> addOrAppendStack(list, itemStack, blockPos1)); 31 | } 32 | 33 | + // Sakura start - configure explosions dropping items 34 | + if (!this.level.sakuraConfig().cannons.explosion.explosionsDropItems) { 35 | + list.clear(); 36 | + } 37 | + // Sakura end - configure explosions dropping items 38 | + 39 | + // Sakura start - configure items that can that drop from explosions 40 | + final Set explosionItemDrops = this.level.sakuraConfig().entity.items.explosionItemDrops.items; 41 | + final boolean whitelist = this.level.sakuraConfig().entity.items.explosionItemDrops.whitelistOverBlacklist; 42 | for (ServerExplosion.StackCollector stackCollector : list) { 43 | + if (explosionItemDrops.contains(stackCollector.stack.getItem()) != whitelist) { 44 | + continue; 45 | + } 46 | + // Sakura end - configure items that can that drop from explosions 47 | Block.popResource(this.level, stackCollector.pos, stackCollector.stack); 48 | } 49 | } 50 | --------------------------------------------------------------------------------