diff --git a/.gitignore b/.gitignore index 6dec446..dad87f4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,10 @@ paper-server paper-api-generator # sakura +build.gradle.kts.rej sakura-api/build.gradle.kts sakura-server/build.gradle.kts -sakura-server/src/vanilla +src/minecraft/ +src/vanilla/ !gradle/wrapper/gradle-wrapper.jar diff --git a/build.gradle.kts b/build.gradle.kts index 9cd270d..a0ffe6d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent plugins { java - id("io.papermc.paperweight.patcher") version "2.0.0-beta.13" + id("io.papermc.paperweight.patcher") version "2.0.0-beta.14" } paperweight { @@ -27,11 +27,6 @@ paperweight { patchesDir = file("$brand-api/paper-patches") outputDir = file("paper-api") } - patchDir("paperApiGenerator") { - upstreamPath = "paper-api-generator" - patchesDir = file("$brand-api-generator/paper-patches") - outputDir = file("paper-api-generator") - } } } diff --git a/gradle.properties b/gradle.properties index 82266e1..eb8389e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group=me.samsuik.sakura version=1.21.4-R0.1-SNAPSHOT mcVersion=1.21.4 -paperRef=8e80d4e15852ffbed1a18d1e9b34550191433200 +paperRef=b1b88cd31687c5b3f80c4b0b51fd93a63b3e2498 org.gradle.jvmargs=-Xmx2G org.gradle.vfs.watch=false diff --git a/migrate/api/0001-Customise-Version-Command.patch b/migrate/api/applied/0001-Customise-Version-Command.patch similarity index 100% rename from migrate/api/0001-Customise-Version-Command.patch rename to migrate/api/applied/0001-Customise-Version-Command.patch diff --git a/migrate/api/0004-isPushedByFluid-API.patch b/migrate/api/applied/0004-isPushedByFluid-API.patch similarity index 100% rename from migrate/api/0004-isPushedByFluid-API.patch rename to migrate/api/applied/0004-isPushedByFluid-API.patch diff --git a/migrate/api/0005-Falling-Block-Parity-API.patch b/migrate/api/applied/0005-Falling-Block-Parity-API.patch similarity index 100% rename from migrate/api/0005-Falling-Block-Parity-API.patch rename to migrate/api/applied/0005-Falling-Block-Parity-API.patch diff --git a/migrate/api/0006-Local-Value-Storage-API.patch b/migrate/api/applied/0006-Local-Value-Storage-API.patch similarity index 100% rename from migrate/api/0006-Local-Value-Storage-API.patch rename to migrate/api/applied/0006-Local-Value-Storage-API.patch diff --git a/migrate/api/0007-Entity-tracking-range-modifier.patch b/migrate/api/applied/0007-Entity-tracking-range-modifier.patch similarity index 100% rename from migrate/api/0007-Entity-tracking-range-modifier.patch rename to migrate/api/applied/0007-Entity-tracking-range-modifier.patch diff --git a/migrate/server/source/0010-Slice-Packet-obfuscation-and-reduction.patch b/migrate/server/feature/0010-Slice-Packet-obfuscation-and-reduction.patch similarity index 100% rename from migrate/server/source/0010-Slice-Packet-obfuscation-and-reduction.patch rename to migrate/server/feature/0010-Slice-Packet-obfuscation-and-reduction.patch diff --git a/migrate/server/source/0002-Sakura-Utils.patch b/migrate/server/source/applied/0002-Sakura-Utils.patch similarity index 100% rename from migrate/server/source/0002-Sakura-Utils.patch rename to migrate/server/source/applied/0002-Sakura-Utils.patch diff --git a/migrate/server/source/0003-Sakura-Configuration-Files.patch b/migrate/server/source/applied/0003-Sakura-Configuration-Files.patch similarity index 100% rename from migrate/server/source/0003-Sakura-Configuration-Files.patch rename to migrate/server/source/applied/0003-Sakura-Configuration-Files.patch diff --git a/migrate/server/source/0004-Local-Config-and-Value-Storage-API.patch b/migrate/server/source/applied/0004-Local-Config-and-Value-Storage-API.patch similarity index 100% rename from migrate/server/source/0004-Local-Config-and-Value-Storage-API.patch rename to migrate/server/source/applied/0004-Local-Config-and-Value-Storage-API.patch diff --git a/migrate/server/source/0006-Optional-Force-Position-Updates.patch b/migrate/server/source/applied/0006-Optional-Force-Position-Updates.patch similarity index 100% rename from migrate/server/source/0006-Optional-Force-Position-Updates.patch rename to migrate/server/source/applied/0006-Optional-Force-Position-Updates.patch diff --git a/migrate/server/source/0008-Track-Tick-Information.patch b/migrate/server/source/applied/0008-Track-Tick-Information.patch similarity index 100% rename from migrate/server/source/0008-Track-Tick-Information.patch rename to migrate/server/source/applied/0008-Track-Tick-Information.patch diff --git a/migrate/server/source/0009-Optimise-New-Liquid-Level.patch b/migrate/server/source/applied/0009-Optimise-New-Liquid-Level.patch similarity index 100% rename from migrate/server/source/0009-Optimise-New-Liquid-Level.patch rename to migrate/server/source/applied/0009-Optimise-New-Liquid-Level.patch diff --git a/migrate/server/source/0011-Use-Optimised-TrackedEntityMap.patch b/migrate/server/source/applied/0011-Use-Optimised-TrackedEntityMap.patch similarity index 100% rename from migrate/server/source/0011-Use-Optimised-TrackedEntityMap.patch rename to migrate/server/source/applied/0011-Use-Optimised-TrackedEntityMap.patch diff --git a/migrate/server/source/0012-Copy-EntityList-methods-to-BasicEntityList.patch b/migrate/server/source/applied/0012-Copy-EntityList-methods-to-BasicEntityList.patch similarity index 100% rename from migrate/server/source/0012-Copy-EntityList-methods-to-BasicEntityList.patch rename to migrate/server/source/applied/0012-Copy-EntityList-methods-to-BasicEntityList.patch diff --git a/migrate/server/source/0013-Add-utility-methods-to-EntitySlices.patch b/migrate/server/source/applied/0013-Add-utility-methods-to-EntitySlices.patch similarity index 100% rename from migrate/server/source/0013-Add-utility-methods-to-EntitySlices.patch rename to migrate/server/source/applied/0013-Add-utility-methods-to-EntitySlices.patch diff --git a/migrate/server/source/0021-Entity-pushed-by-fluid-API.patch b/migrate/server/source/applied/0021-Entity-pushed-by-fluid-API.patch similarity index 100% rename from migrate/server/source/0021-Entity-pushed-by-fluid-API.patch rename to migrate/server/source/applied/0021-Entity-pushed-by-fluid-API.patch diff --git a/migrate/server/source/0022-Cannon-Mechanics.patch b/migrate/server/source/applied/0022-Cannon-Mechanics.patch similarity index 100% rename from migrate/server/source/0022-Cannon-Mechanics.patch rename to migrate/server/source/applied/0022-Cannon-Mechanics.patch diff --git a/migrate/server/source/0023-Cache-MovingBlockEntity-collision-shape.patch b/migrate/server/source/applied/0023-Cache-MovingBlockEntity-collision-shape.patch similarity index 100% rename from migrate/server/source/0023-Cache-MovingBlockEntity-collision-shape.patch rename to migrate/server/source/applied/0023-Cache-MovingBlockEntity-collision-shape.patch diff --git a/migrate/server/source/0024-Optimise-TNT-fluid-state.patch b/migrate/server/source/applied/0024-Optimise-TNT-fluid-state.patch similarity index 100% rename from migrate/server/source/0024-Optimise-TNT-fluid-state.patch rename to migrate/server/source/applied/0024-Optimise-TNT-fluid-state.patch diff --git a/migrate/server/source/0027-Despawn-falling-blocks-inside-moving-pistons.patch b/migrate/server/source/applied/0027-Despawn-falling-blocks-inside-moving-pistons.patch similarity index 100% rename from migrate/server/source/0027-Despawn-falling-blocks-inside-moving-pistons.patch rename to migrate/server/source/applied/0027-Despawn-falling-blocks-inside-moving-pistons.patch diff --git a/migrate/server/source/0028-Configure-Entity-Knockback.patch b/migrate/server/source/applied/0028-Configure-Entity-Knockback.patch similarity index 100% rename from migrate/server/source/0028-Configure-Entity-Knockback.patch rename to migrate/server/source/applied/0028-Configure-Entity-Knockback.patch diff --git a/migrate/server/source/0031-Falling-Block-Stacking-Restrictions.patch b/migrate/server/source/applied/0031-Falling-Block-Stacking-Restrictions.patch similarity index 100% rename from migrate/server/source/0031-Falling-Block-Stacking-Restrictions.patch rename to migrate/server/source/applied/0031-Falling-Block-Stacking-Restrictions.patch diff --git a/migrate/server/source/0032-Added-list-of-ItemEntity-s-that-ignore-explosions.patch b/migrate/server/source/applied/0032-Added-list-of-ItemEntity-s-that-ignore-explosions.patch similarity index 100% rename from migrate/server/source/0032-Added-list-of-ItemEntity-s-that-ignore-explosions.patch rename to migrate/server/source/applied/0032-Added-list-of-ItemEntity-s-that-ignore-explosions.patch diff --git a/migrate/server/source/0033-Add-option-to-disable-entity-ai.patch b/migrate/server/source/applied/0033-Add-option-to-disable-entity-ai.patch similarity index 100% rename from migrate/server/source/0033-Add-option-to-disable-entity-ai.patch rename to migrate/server/source/applied/0033-Add-option-to-disable-entity-ai.patch diff --git a/migrate/server/source/0034-Consistent-Explosion-Radius.patch b/migrate/server/source/applied/0034-Consistent-Explosion-Radius.patch similarity index 100% rename from migrate/server/source/0034-Consistent-Explosion-Radius.patch rename to migrate/server/source/applied/0034-Consistent-Explosion-Radius.patch diff --git a/migrate/server/source/0035-Remove-spigot-max-tnt-per-tick.patch b/migrate/server/source/applied/0035-Remove-spigot-max-tnt-per-tick.patch similarity index 100% rename from migrate/server/source/0035-Remove-spigot-max-tnt-per-tick.patch rename to migrate/server/source/applied/0035-Remove-spigot-max-tnt-per-tick.patch diff --git a/migrate/server/source/0036-Option-to-configure-entity-water-sensitivity.patch b/migrate/server/source/applied/0036-Option-to-configure-entity-water-sensitivity.patch similarity index 100% rename from migrate/server/source/0036-Option-to-configure-entity-water-sensitivity.patch rename to migrate/server/source/applied/0036-Option-to-configure-entity-water-sensitivity.patch diff --git a/migrate/server/source/0038-Add-redstone-implementation-API.patch b/migrate/server/source/applied/0038-Add-redstone-implementation-API.patch similarity index 100% rename from migrate/server/source/0038-Add-redstone-implementation-API.patch rename to migrate/server/source/applied/0038-Add-redstone-implementation-API.patch diff --git a/migrate/server/source/0039-Allow-water-in-the-nether.patch b/migrate/server/source/applied/0039-Allow-water-in-the-nether.patch similarity index 100% rename from migrate/server/source/0039-Allow-water-in-the-nether.patch rename to migrate/server/source/applied/0039-Allow-water-in-the-nether.patch diff --git a/migrate/server/source/0040-Configure-concrete-solidifying-in-water.patch b/migrate/server/source/applied/0040-Configure-concrete-solidifying-in-water.patch similarity index 100% rename from migrate/server/source/0040-Configure-concrete-solidifying-in-water.patch rename to migrate/server/source/applied/0040-Configure-concrete-solidifying-in-water.patch diff --git a/migrate/server/source/0041-Option-for-fast-nether-dimension-lava.patch b/migrate/server/source/applied/0041-Option-for-fast-nether-dimension-lava.patch similarity index 100% rename from migrate/server/source/0041-Option-for-fast-nether-dimension-lava.patch rename to migrate/server/source/applied/0041-Option-for-fast-nether-dimension-lava.patch diff --git a/migrate/server/source/0043-Disable-bubble-columns-affecting-cannon-entities.patch b/migrate/server/source/applied/0043-Disable-bubble-columns-affecting-cannon-entities.patch similarity index 100% rename from migrate/server/source/0043-Disable-bubble-columns-affecting-cannon-entities.patch rename to migrate/server/source/applied/0043-Disable-bubble-columns-affecting-cannon-entities.patch diff --git a/migrate/server/source/0047-Configure-mob-spawner-defaults.patch b/migrate/server/source/applied/0047-Configure-mob-spawner-defaults.patch similarity index 100% rename from migrate/server/source/0047-Configure-mob-spawner-defaults.patch rename to migrate/server/source/applied/0047-Configure-mob-spawner-defaults.patch diff --git a/migrate/server/source/0048-Allow-disabling-random-dispenser-item-selection.patch b/migrate/server/source/applied/0048-Allow-disabling-random-dispenser-item-selection.patch similarity index 100% rename from migrate/server/source/0048-Allow-disabling-random-dispenser-item-selection.patch rename to migrate/server/source/applied/0048-Allow-disabling-random-dispenser-item-selection.patch diff --git a/migrate/server/source/0049-Add-instant-mob-death-animation.patch b/migrate/server/source/applied/0049-Add-instant-mob-death-animation.patch similarity index 100% rename from migrate/server/source/0049-Add-instant-mob-death-animation.patch rename to migrate/server/source/applied/0049-Add-instant-mob-death-animation.patch diff --git a/migrate/server/source/0050-Configure-fluids-breaking-redstone.patch b/migrate/server/source/applied/0050-Configure-fluids-breaking-redstone.patch similarity index 100% rename from migrate/server/source/0050-Configure-fluids-breaking-redstone.patch rename to migrate/server/source/applied/0050-Configure-fluids-breaking-redstone.patch diff --git a/migrate/server/source/0051-Option-to-disable-explosions-hurting-players.patch b/migrate/server/source/applied/0051-Option-to-disable-explosions-hurting-players.patch similarity index 100% rename from migrate/server/source/0051-Option-to-disable-explosions-hurting-players.patch rename to migrate/server/source/applied/0051-Option-to-disable-explosions-hurting-players.patch diff --git a/migrate/server/source/0052-Iron-golems-take-fall-damage.patch b/migrate/server/source/applied/0052-Iron-golems-take-fall-damage.patch similarity index 100% rename from migrate/server/source/0052-Iron-golems-take-fall-damage.patch rename to migrate/server/source/applied/0052-Iron-golems-take-fall-damage.patch diff --git a/migrate/server/source/0053-Add-explosions-dropping-items-config.patch b/migrate/server/source/applied/0053-Add-explosions-dropping-items-config.patch similarity index 100% rename from migrate/server/source/0053-Add-explosions-dropping-items-config.patch rename to migrate/server/source/applied/0053-Add-explosions-dropping-items-config.patch diff --git a/migrate/server/source/0054-Avoid-searching-for-lava-if-throttled-water-flow-spe.patch b/migrate/server/source/applied/0054-Avoid-searching-for-lava-if-throttled-water-flow-spe.patch similarity index 100% rename from migrate/server/source/0054-Avoid-searching-for-lava-if-throttled-water-flow-spe.patch rename to migrate/server/source/applied/0054-Avoid-searching-for-lava-if-throttled-water-flow-spe.patch diff --git a/migrate/server/source/0055-Calculate-biome-noise-once-per-chunk-section.patch b/migrate/server/source/applied/0055-Calculate-biome-noise-once-per-chunk-section.patch similarity index 100% rename from migrate/server/source/0055-Calculate-biome-noise-once-per-chunk-section.patch rename to migrate/server/source/applied/0055-Calculate-biome-noise-once-per-chunk-section.patch diff --git a/migrate/server/source/0056-Fix-doEntityDrops-gamerule-preventing-falling-blocks.patch b/migrate/server/source/applied/0056-Fix-doEntityDrops-gamerule-preventing-falling-blocks.patch similarity index 100% rename from migrate/server/source/0056-Fix-doEntityDrops-gamerule-preventing-falling-blocks.patch rename to migrate/server/source/applied/0056-Fix-doEntityDrops-gamerule-preventing-falling-blocks.patch diff --git a/migrate/server/source/0058-Configure-potion-speed-and-breaking-inside-entities.patch b/migrate/server/source/applied/0058-Configure-potion-speed-and-breaking-inside-entities.patch similarity index 100% rename from migrate/server/source/0058-Configure-potion-speed-and-breaking-inside-entities.patch rename to migrate/server/source/applied/0058-Configure-potion-speed-and-breaking-inside-entities.patch diff --git a/migrate/server/source/0059-Add-outline-colliison-to-enderpearls.patch b/migrate/server/source/applied/0059-Add-outline-colliison-to-enderpearls.patch similarity index 100% rename from migrate/server/source/0059-Add-outline-colliison-to-enderpearls.patch rename to migrate/server/source/applied/0059-Add-outline-colliison-to-enderpearls.patch diff --git a/migrate/server/source/0060-Disable-player-poses-shrinking-collision-box.patch b/migrate/server/source/applied/0060-Disable-player-poses-shrinking-collision-box.patch similarity index 100% rename from migrate/server/source/0060-Disable-player-poses-shrinking-collision-box.patch rename to migrate/server/source/applied/0060-Disable-player-poses-shrinking-collision-box.patch diff --git a/migrate/server/source/0061-Mob-spawner-behaviour.patch b/migrate/server/source/applied/0061-Mob-spawner-behaviour.patch similarity index 100% rename from migrate/server/source/0061-Mob-spawner-behaviour.patch rename to migrate/server/source/applied/0061-Mob-spawner-behaviour.patch diff --git a/migrate/server/source/0062-Use-random-chance-for-crop-growth-instead-of-age.patch b/migrate/server/source/applied/0062-Use-random-chance-for-crop-growth-instead-of-age.patch similarity index 100% rename from migrate/server/source/0062-Use-random-chance-for-crop-growth-instead-of-age.patch rename to migrate/server/source/applied/0062-Use-random-chance-for-crop-growth-instead-of-age.patch diff --git a/migrate/server/source/0063-Protect-block-shapes-against-plugins.patch b/migrate/server/source/applied/0063-Protect-block-shapes-against-plugins.patch similarity index 100% rename from migrate/server/source/0063-Protect-block-shapes-against-plugins.patch rename to migrate/server/source/applied/0063-Protect-block-shapes-against-plugins.patch diff --git a/migrate/server/source/0064-Legacy-player-combat-mechanics.patch b/migrate/server/source/applied/0064-Legacy-player-combat-mechanics.patch similarity index 100% rename from migrate/server/source/0064-Legacy-player-combat-mechanics.patch rename to migrate/server/source/applied/0064-Legacy-player-combat-mechanics.patch diff --git a/migrate/server/source/0065-Allow-disabling-sweep-attacks.patch b/migrate/server/source/applied/0065-Allow-disabling-sweep-attacks.patch similarity index 100% rename from migrate/server/source/0065-Allow-disabling-sweep-attacks.patch rename to migrate/server/source/applied/0065-Allow-disabling-sweep-attacks.patch diff --git a/migrate/server/source/0066-Change-shields-to-reduce-damage.patch b/migrate/server/source/applied/0066-Change-shields-to-reduce-damage.patch similarity index 100% rename from migrate/server/source/0066-Change-shields-to-reduce-damage.patch rename to migrate/server/source/applied/0066-Change-shields-to-reduce-damage.patch diff --git a/migrate/server/source/0067-Old-enchanted-golden-apples.patch b/migrate/server/source/applied/0067-Old-enchanted-golden-apples.patch similarity index 100% rename from migrate/server/source/0067-Old-enchanted-golden-apples.patch rename to migrate/server/source/applied/0067-Old-enchanted-golden-apples.patch diff --git a/migrate/server/source/0068-Configure-fast-health-regen.patch b/migrate/server/source/applied/0068-Configure-fast-health-regen.patch similarity index 100% rename from migrate/server/source/0068-Configure-fast-health-regen.patch rename to migrate/server/source/applied/0068-Configure-fast-health-regen.patch diff --git a/migrate/server/source/0069-Add-option-for-fishing-hooks-pulling-entities.patch b/migrate/server/source/applied/0069-Add-option-for-fishing-hooks-pulling-entities.patch similarity index 100% rename from migrate/server/source/0069-Add-option-for-fishing-hooks-pulling-entities.patch rename to migrate/server/source/applied/0069-Add-option-for-fishing-hooks-pulling-entities.patch diff --git a/migrate/server/source/0070-Old-combat-sounds-and-particle-effects.patch b/migrate/server/source/applied/0070-Old-combat-sounds-and-particle-effects.patch similarity index 100% rename from migrate/server/source/0070-Old-combat-sounds-and-particle-effects.patch rename to migrate/server/source/applied/0070-Old-combat-sounds-and-particle-effects.patch diff --git a/migrate/server/source/0072-Entity-tracking-range-modifier.patch b/migrate/server/source/applied/0072-Entity-tracking-range-modifier.patch similarity index 100% rename from migrate/server/source/0072-Entity-tracking-range-modifier.patch rename to migrate/server/source/applied/0072-Entity-tracking-range-modifier.patch diff --git a/migrate/server/source/0077-Add-max-armour-durability-damage.patch b/migrate/server/source/applied/0077-Add-max-armour-durability-damage.patch similarity index 100% rename from migrate/server/source/0077-Add-max-armour-durability-damage.patch rename to migrate/server/source/applied/0077-Add-max-armour-durability-damage.patch diff --git a/migrate/server/source/0078-Modify-bucket-stack-size.patch b/migrate/server/source/applied/0078-Modify-bucket-stack-size.patch similarity index 100% rename from migrate/server/source/0078-Modify-bucket-stack-size.patch rename to migrate/server/source/applied/0078-Modify-bucket-stack-size.patch diff --git a/migrate/server/source/0080-Configure-TNT-duplication.patch b/migrate/server/source/applied/0080-Configure-TNT-duplication.patch similarity index 100% rename from migrate/server/source/0080-Configure-TNT-duplication.patch rename to migrate/server/source/applied/0080-Configure-TNT-duplication.patch diff --git a/migrate/server/source/0081-Add-lava-flow-speed-api.patch b/migrate/server/source/applied/0081-Add-lava-flow-speed-api.patch similarity index 100% rename from migrate/server/source/0081-Add-lava-flow-speed-api.patch rename to migrate/server/source/applied/0081-Add-lava-flow-speed-api.patch diff --git a/migrate/server/source/0073-Set-entity-impulse-on-explosion.patch b/migrate/server/source/removed/0073-Set-entity-impulse-on-explosion.patch similarity index 100% rename from migrate/server/source/0073-Set-entity-impulse-on-explosion.patch rename to migrate/server/source/removed/0073-Set-entity-impulse-on-explosion.patch diff --git a/sakura-api/build.gradle.kts.patch b/sakura-api/build.gradle.kts.patch new file mode 100644 index 0000000..0313e40 --- /dev/null +++ b/sakura-api/build.gradle.kts.patch @@ -0,0 +1,53 @@ +--- a/paper-api/build.gradle.kts ++++ b/paper-api/build.gradle.kts +@@ -93,7 +_,7 @@ + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + } + +-val generatedApiPath: java.nio.file.Path = layout.projectDirectory.dir("src/generated/java").asFile.toPath() ++val generatedApiPath: java.nio.file.Path = rootProject.layout.projectDirectory.dir("paper-api/src/generated/java").asFile.toPath() + idea { + module { + generatedSourceDirs.add(generatedApiPath.toFile()) +@@ -103,6 +_,18 @@ + main { + java { + srcDir(generatedApiPath) ++ srcDir(file("../paper-api/src/main/java")) ++ } ++ resources { ++ srcDir(file("../paper-api/src/main/resources")) ++ } ++ } ++ test { ++ java { ++ srcDir(file("../paper-api/src/test/java")) ++ } ++ resources { ++ srcDir(file("../paper-api/src/test/resources")) + } + } + } +@@ -169,7 +_,7 @@ + + tasks.withType { + val options = options as StandardJavadocDocletOptions +- options.overview = "src/main/javadoc/overview.html" ++ options.overview = "../paper-api/src/main/javadoc/overview.html" + options.use() + options.isDocFilesSubDirs = true + options.links( +@@ -202,11 +_,11 @@ + } + + // workaround for https://github.com/gradle/gradle/issues/4046 +- inputs.dir("src/main/javadoc").withPropertyName("javadoc-sourceset") ++ inputs.dir("../paper-api/src/main/javadoc").withPropertyName("javadoc-sourceset") + val fsOps = services.fileSystemOperations + doLast { + fsOps.copy { +- from("src/main/javadoc") { ++ from("../paper-api/src/main/javadoc") { + include("**/doc-files/**") + } + into("build/docs/javadoc") diff --git a/sakura-api/paper-patches/files/src/main/java/org/bukkit/Bukkit.java.patch b/sakura-api/paper-patches/files/src/main/java/org/bukkit/Bukkit.java.patch new file mode 100644 index 0000000..9a3946c --- /dev/null +++ b/sakura-api/paper-patches/files/src/main/java/org/bukkit/Bukkit.java.patch @@ -0,0 +1,23 @@ +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -126,6 +_,20 @@ + // Paper end + } + ++ // Sakura start - customise version command; expose git information ++ @NotNull ++ public static String getGitInformation() { ++ final io.papermc.paper.ServerBuildInfo version = io.papermc.paper.ServerBuildInfo.buildInfo(); ++ final String gitBranch = version.gitBranch().orElse("Dev"); ++ final String gitCommit = version.gitCommit().orElse(""); ++ String branchMsg = " on " + gitBranch; ++ if ("master".equals(gitBranch) || "main".equals(gitBranch)) { ++ branchMsg = ""; // Don't show branch on main/master ++ } ++ return "(Git: " + gitCommit + branchMsg + ")"; ++ } ++ // Sakura end - customise version command; expose git information ++ + /** + * Gets the name of this server implementation. + * diff --git a/sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch b/sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch new file mode 100644 index 0000000..1fded9a --- /dev/null +++ b/sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch @@ -0,0 +1,13 @@ +--- a/src/main/java/org/bukkit/World.java ++++ b/src/main/java/org/bukkit/World.java +@@ -205,6 +_,10 @@ + return new Location(this, x, y, z); + } + // Paper end ++ // Sakura start ++ @NotNull ++ me.samsuik.sakura.local.storage.LocalStorageHandler getStorageHandler(); ++ // Sakura end + + /** + * Gets the highest non-empty (impassable) block at the given coordinates. diff --git a/sakura-api/paper-patches/files/src/main/java/org/bukkit/command/defaults/VersionCommand.java.patch b/sakura-api/paper-patches/files/src/main/java/org/bukkit/command/defaults/VersionCommand.java.patch new file mode 100644 index 0000000..8121fcb --- /dev/null +++ b/sakura-api/paper-patches/files/src/main/java/org/bukkit/command/defaults/VersionCommand.java.patch @@ -0,0 +1,51 @@ +--- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +@@ -33,6 +_,11 @@ + import net.kyori.adventure.text.format.TextDecoration; + import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; + // Paper end - version command 2.0 ++// Sakura start - customise version command ++import net.kyori.adventure.text.event.HoverEvent; ++import net.kyori.adventure.text.minimessage.MiniMessage; ++import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; ++// Sakura end - customise version command + + public class VersionCommand extends BukkitCommand { + private VersionFetcher versionFetcher; // Paper - version command 2.0 +@@ -44,6 +_,15 @@ + return versionFetcher; + } + ++ // Sakura start - customise version command ++ private static final String VERSION_MESSAGE = """ ++ . ++ | This server is running Sakura ++ | Commit: \\<> targeting (MC: ) ++ | Github: \\<link> ++ '"""; ++ // Sakura end - customise version command ++ + public VersionCommand(@NotNull String name) { + super(name); + +@@ -55,11 +_,16 @@ + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { +- if (!testPermission(sender)) return true; +- +- if (args.length == 0) { ++ // Sakura start - customise version command ++ if (args.length == 0 || !this.testPermission(sender)) { ++ sender.sendMessage(MiniMessage.miniMessage().deserialize(VERSION_MESSAGE, ++ Placeholder.component("commit", Component.text("hover", NamedTextColor.YELLOW) ++ .hoverEvent(HoverEvent.showText(Component.text(Bukkit.getGitInformation())))), ++ Placeholder.unparsed("version", Bukkit.getMinecraftVersion()) ++ )); + //sender.sendMessage("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); // Paper - moved to setVersionMessage +- sendVersion(sender); ++ //sendVersion(sender); ++ // Sakura end - customise version command + } else { + StringBuilder name = new StringBuilder(); + diff --git a/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Entity.java.patch b/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Entity.java.patch new file mode 100644 index 0000000..ebe29e8 --- /dev/null +++ b/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Entity.java.patch @@ -0,0 +1,25 @@ +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -35,6 +_,22 @@ + */ + public interface Entity extends Metadatable, CommandSender, Nameable, PersistentDataHolder, net.kyori.adventure.text.event.HoverEventSource, net.kyori.adventure.sound.Sound.Emitter { // Paper + ++ // Sakura start - entity pushed by fluid api ++ /** ++ * Gets if the entity will be pushed by fluid. ++ * ++ * @return if this entity can be pushed by fluid. ++ */ ++ boolean isPushedByFluid(); ++ ++ /** ++ * Sets if the entity will be pushed by fluid. ++ * ++ * @param state whether entity should be pushed by fluid ++ */ ++ void setPushedByFluid(boolean state); ++ // Sakura end - entity pushed by fluid api ++ + /** + * Gets the entity's current position + * diff --git a/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/FallingBlock.java.patch b/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/FallingBlock.java.patch new file mode 100644 index 0000000..d1c9100 --- /dev/null +++ b/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/FallingBlock.java.patch @@ -0,0 +1,25 @@ +--- a/src/main/java/org/bukkit/entity/FallingBlock.java ++++ b/src/main/java/org/bukkit/entity/FallingBlock.java +@@ -9,6 +_,22 @@ + */ + public interface FallingBlock extends Entity { + ++ // Sakura start - falling block height parity api ++ /** ++ * Gets if falling block has height parity ++ * ++ * @return parity ++ */ ++ boolean getHeightParity(); ++ ++ /** ++ * Sets falling block height parity ++ * ++ * @param parity value ++ */ ++ void setHeightParity(boolean parity); ++ // Sakura end - falling block height parity api ++ + /** + * Get the Material of the falling block + * diff --git a/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Player.java.patch b/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Player.java.patch new file mode 100644 index 0000000..a9ff07b --- /dev/null +++ b/sakura-api/paper-patches/files/src/main/java/org/bukkit/entity/Player.java.patch @@ -0,0 +1,15 @@ +--- a/src/main/java/org/bukkit/entity/Player.java ++++ b/src/main/java/org/bukkit/entity/Player.java +@@ -61,6 +_,12 @@ + */ + public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified, net.kyori.adventure.bossbar.BossBarViewer, com.destroystokyo.paper.network.NetworkClient { // Paper + ++ // Sakura start - entity tracking range modifier ++ double getTrackingRangeModifier(); ++ ++ void setTrackingRangeModifier(double mod); ++ // Sakura end - entity tracking range modifier ++ + // Paper start + @Override + default net.kyori.adventure.identity.@NotNull Identity identity() { diff --git a/sakura-api/src/main/java/me/samsuik/sakura/entity/merge/MergeLevel.java b/sakura-api/src/main/java/me/samsuik/sakura/entity/merge/MergeLevel.java new file mode 100644 index 0000000..f8010a9 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/entity/merge/MergeLevel.java @@ -0,0 +1,42 @@ +package me.samsuik.sakura.entity.merge; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public enum MergeLevel { + /** + * Disabled. + */ + NONE(-1), + /** + * "STRICT" merges entities with the same properties, position, momentum and OOE. + * This is considered safe to use, and will not break cannon mechanics. + */ + STRICT(1), + /** + * "LENIENT" merges entities aggressively by tracking the entities that have + * previously merged. This is a hybrid of "SPAWN" and "STRICT" merging, with the + * visuals of "STRICT" merging and better merging potential of "SPAWN" merging. + */ + LENIENT(2), + /** + * "SPAWN" merges entities one gametick after they have spawned. Merging is + * only possible after it has been established that the entity is safe to + * merge by collecting information on the entities that merge together over time. + */ + SPAWN(3); + + private final int level; + + MergeLevel(int level) { + this.level = level; + } + + public boolean atLeast(MergeLevel level) { + return this.getLevel() >= level.getLevel(); + } + + public int getLevel() { + return this.level; + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/entity/merge/Mergeable.java b/sakura-api/src/main/java/me/samsuik/sakura/entity/merge/Mergeable.java new file mode 100644 index 0000000..c33c461 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/entity/merge/Mergeable.java @@ -0,0 +1,14 @@ +package me.samsuik.sakura.entity.merge; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface Mergeable { + MergeLevel getMergeLevel(); + + void setMergeLevel(MergeLevel level); + + int getStacked(); + + void setStacked(int stacked); +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java new file mode 100644 index 0000000..e9c29d2 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java @@ -0,0 +1,46 @@ +package me.samsuik.sakura.local; + +import io.papermc.paper.math.Position; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record LocalRegion(int minX, int minZ, int maxX, int maxZ) { + public static LocalRegion from(BoundingBox boundingBox) { + return of(boundingBox.getMin(), boundingBox.getMax()); + } + + public static LocalRegion of(Vector min, Vector max) { + return of(min.getBlockX(), min.getBlockZ(), max.getBlockX(), max.getBlockZ()); + } + + public static LocalRegion of(Position min, Position max) { + return of(min.blockX(), min.blockZ(), max.blockX(), max.blockZ()); + } + + public static LocalRegion of(int minX, int minZ, int maxX, int maxZ) { + return new LocalRegion( + Math.min(minX, maxX), Math.min(minZ, maxZ), + Math.max(minX, maxX), Math.max(minZ, maxZ) + ); + } + + public static LocalRegion at(int x, int z, int radius) { + return new LocalRegion(x-radius, z-radius, x+radius, z+radius); + } + + public boolean intersects(LocalRegion region) { + return (this.minX < region.minX() && this.maxX > region.minX() || this.maxX > region.maxX() && this.minX < region.maxX()) + && (this.minZ < region.minZ() && this.maxZ > region.minZ() || this.maxZ > region.maxZ() && this.minZ < region.maxZ()); + } + + public boolean contains(LocalRegion region) { + return this.minX < region.minX() && this.maxX > region.maxX() + && this.maxZ < region.minZ() && this.maxZ > region.maxZ(); + } + + public boolean contains(int x, int z) { + return this.minX <= x && this.maxX >= x && this.minZ <= z && this.maxZ >= z; + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKey.java b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKey.java new file mode 100644 index 0000000..4a1540d --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKey.java @@ -0,0 +1,23 @@ +package me.samsuik.sakura.local; + +import org.bukkit.NamespacedKey; +import org.jspecify.annotations.NullMarked; + +import java.util.function.Supplier; + +@NullMarked +public record LocalValueKey(NamespacedKey key, Supplier defaultSupplier) { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || this.getClass() != o.getClass()) return false; + + LocalValueKey that = (LocalValueKey) o; + return this.key.equals(that.key); + } + + @Override + public int hashCode() { + return this.key.hashCode(); + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKeys.java b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKeys.java new file mode 100644 index 0000000..54c3693 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKeys.java @@ -0,0 +1,25 @@ +package me.samsuik.sakura.local; + +import me.samsuik.sakura.physics.PhysicsVersion; +import me.samsuik.sakura.redstone.RedstoneImplementation; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public final class LocalValueKeys { + private static final String NAMESPACE = "sakura"; + + public static final LocalValueKey PHYSICS_VERSION = create("physics-version", () -> PhysicsVersion.LATEST); + public static final LocalValueKey>> DURABLE_MATERIALS = create("durable-materials", HashMap::new); + public static final LocalValueKey REDSTONE_IMPLEMENTATION = create("redstone-implementation", () -> RedstoneImplementation.VANILLA); + public static final LocalValueKey CONSISTENT_EXPLOSION_RADIUS = create("consistent-radius", () -> false); + public static final LocalValueKey REDSTONE_CACHE = create("redstone-cache", () -> false); + public static final LocalValueKey LAVA_FLOW_SPEED = create("lava-flow-speed", () -> -1); + + private static LocalValueKey create(String key, Supplier supplier) { + return new LocalValueKey<>(new NamespacedKey(NAMESPACE, key), supplier); + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalStorageHandler.java b/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalStorageHandler.java new file mode 100644 index 0000000..819e278 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalStorageHandler.java @@ -0,0 +1,27 @@ +package me.samsuik.sakura.local.storage; + +import me.samsuik.sakura.local.LocalRegion; +import org.bukkit.Location; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public interface LocalStorageHandler { + default @NonNull Optional locate(@NonNull Location location) { + return this.locate(location.blockX(), location.blockZ()); + } + + @NonNull Optional locate(int x, int z); + + @Nullable LocalValueStorage get(@NonNull LocalRegion region); + + boolean has(@NonNull LocalRegion region); + + void put(@NonNull LocalRegion region, @NonNull LocalValueStorage storage); + + void remove(@NonNull LocalRegion region); + + @NonNull List regions(); +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalValueStorage.java b/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalValueStorage.java new file mode 100644 index 0000000..cbaaa24 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalValueStorage.java @@ -0,0 +1,50 @@ +package me.samsuik.sakura.local.storage; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import me.samsuik.sakura.local.LocalValueKey; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Map; +import java.util.Optional; + +@NullMarked +@SuppressWarnings("unchecked") +public final class LocalValueStorage { + private final Map, Object> map = new Object2ObjectOpenHashMap<>(); + + public void set(LocalValueKey key, T insert) { + this.map.put(key, insert); + } + + public void remove(LocalValueKey key) { + this.map.remove(key); + } + + public Optional get(LocalValueKey key) { + T value = (T) this.map.get(key); + return Optional.ofNullable(value); + } + + public T getOrDefault(LocalValueKey key, T def) { + return (T) this.map.getOrDefault(key, def); + } + + public boolean exists(LocalValueKey key) { + return this.map.containsKey(key); + } + + @Nullable + public T value(LocalValueKey key) { + return (T) this.map.get(key); + } + + public T value(LocalValueKey key, boolean returnDefault) { + T val = (T) this.map.get(key); + if (!returnDefault || val != null) + return val; + // update value + this.set(key, val = key.defaultSupplier().get()); + return val; + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/physics/PhysicsVersion.java b/sakura-api/src/main/java/me/samsuik/sakura/physics/PhysicsVersion.java new file mode 100644 index 0000000..685fefb --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/physics/PhysicsVersion.java @@ -0,0 +1,74 @@ +package me.samsuik.sakura.physics; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public enum PhysicsVersion { + LEGACY("legacy", 1_0_0), // replicates patched 1.8.8 paper mechanics + v1_8_2("1.8.2", 1_8_2), // vanilla mechanics + v1_9("1.9", 1_9_0), + v1_10("1.10", 1_10_0), + v1_11("1.11", 1_11_0), + v1_12("1.12", 1_12_0), + v1_13("1.13", 1_13_0), + v1_14("1.14", 1_14_0), + v1_16("1.16", 1_16_0), + v1_17("1.17", 1_17_0), + v1_18_2("1.18.2", 1_18_2), + v1_19_3("1.19.3", 1_19_3), + v1_20("1.20", 1_20_0), + v1_21_2("1.21.2", 1_21_2), + LATEST("latest", 9_99_9); // latest version + + private final String friendlyName; + private final int version; + + PhysicsVersion(String friendlyName, int version) { + this.friendlyName = friendlyName; + this.version = version; + } + + public boolean isLegacy() { + return this == LEGACY; + } + + public boolean afterOrEqual(int version) { + return this.version >= version; + } + + public boolean before(int version) { + return this.version < version; + } + + public boolean is(int version) { + return this.version == version; + } + + public boolean isWithin(int min, int max) { + return this.version >= min && this.version <= max; + } + + public int getVersion() { + return this.version; + } + + public String getFriendlyName() { + return this.friendlyName; + } + + public static PhysicsVersion from(String string) { + int parsedVersion = Integer.MIN_VALUE; + try { + String versionString = string.replace(".", ""); + parsedVersion = Integer.parseInt(versionString); + } catch (NumberFormatException nfe) { + // ignored + } + for (PhysicsVersion ver : values()) { + if (ver.name().equalsIgnoreCase(string) || ver.getFriendlyName().equalsIgnoreCase(string) || ver.is(parsedVersion)) { + return ver; + } + } + return LATEST; + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java b/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java new file mode 100644 index 0000000..bfb5e9e --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java @@ -0,0 +1,20 @@ +package me.samsuik.sakura.redstone; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public enum RedstoneImplementation { + VANILLA("vanilla"), + EIGENCRAFT("eigencraft"), + ALTERNATE_CURRENT("alternate-current"); + + private final String friendlyName; + + RedstoneImplementation(String friendlyName) { + this.friendlyName = friendlyName; + } + + public String getFriendlyName() { + return this.friendlyName; + } +} diff --git a/sakura-server/build.gradle.kts.patch b/sakura-server/build.gradle.kts.patch index 0371c4b..f8d11db 100644 --- a/sakura-server/build.gradle.kts.patch +++ b/sakura-server/build.gradle.kts.patch @@ -8,7 +8,7 @@ dependencies { mache("io.papermc:mache:1.21.4+build.7") -@@ -21,6 +_,21 @@ +@@ -21,6 +_,17 @@ // macheOldPath = file("F:\\Projects\\PaperTooling\\mache\\versions\\1.21.4\\src\\main\\java") // gitFilePatches = true @@ -22,10 +22,6 @@ + } + + activeFork = fork -+ -+ paper { -+ paperServerDir = upstreamsDirectory().map { it.dir("paper/paper-server") } -+ } + spigot { buildDataRef = "3edaf46ec1eed4115ce1b18d2846cded42577e42" diff --git a/sakura-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java.patch b/sakura-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java.patch new file mode 100644 index 0000000..d8042ea --- /dev/null +++ b/sakura-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java.patch @@ -0,0 +1,110 @@ +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +@@ -297,6 +_,12 @@ + return true; + } + ++ // Sakura start - add utility methods to entity slices ++ public Entity[] getSectionEntities(int sectionY) { ++ return this.allEntities.getSectionEntities(sectionY); ++ } ++ // Sakura end - add utility methods to entity slices ++ + public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + this.hardCollidingEntities.getEntities(except, box, into, predicate); + } +@@ -383,6 +_,13 @@ + + private E[] storage; + private int size; ++ // Sakura start - use methods from EntityList ++ private it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap entityToIndex = null; ++ private void setupIndexMap() { ++ this.entityToIndex = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(2, 0.8f); ++ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ // Sakura end - use methods from EntityList + + public BasicEntityList() { + this(0); +@@ -403,6 +_,7 @@ + private void resize() { + if (this.storage == EMPTY) { + this.storage = (E[])new Entity[DEFAULT_CAPACITY]; ++ this.setupIndexMap(); // Sakura - use methods from EntityList + } else { + this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); + } +@@ -416,6 +_,7 @@ + } else { + this.storage[idx] = entity; + } ++ this.entityToIndex.put(entity.getId(), idx); // Sakura - use methods from EntityList + } + + public int indexOf(final E entity) { +@@ -431,24 +_,32 @@ + } + + public boolean remove(final E entity) { +- final int idx = this.indexOf(entity); +- if (idx == -1) { +- return false; +- } +- +- final int size = --this.size; +- final E[] storage = this.storage; +- if (idx != size) { +- System.arraycopy(storage, idx + 1, storage, idx, size - idx); +- } +- +- storage[size] = null; ++ // Sakura start - use methods from EntityList ++ if (this.entityToIndex == null) { ++ return false; ++ } ++ ++ final int index = this.entityToIndex.remove(entity.getId()); ++ if (index == Integer.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entity at the end to this index ++ final int endIndex = --this.size; ++ final E end = this.storage[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.entityToIndex.put(end.getId(), index); // update index ++ } ++ this.storage[index] = end; ++ this.storage[endIndex] = null; ++ // Sakura end - use methods from EntityList + + return true; + } + + public boolean has(final E entity) { +- return this.indexOf(entity) != -1; ++ return this.entityToIndex != null && this.entityToIndex.containsKey(entity.getId()); // Sakura - use methods from EntityList + } + } + +@@ -494,6 +_,18 @@ + this.entitiesBySection[sectionIndex] = null; + } + } ++ ++ // Sakura start - add utility methods to entity slices ++ public Entity[] getSectionEntities(int sectionY) { ++ BasicEntityList list = this.entitiesBySection[sectionY - this.slices.minSection]; ++ ++ if (list != null) { ++ return list.storage; ++ } ++ ++ return new Entity[0]; ++ } ++ // Sakura end - add utility methods to entity slices + + public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + if (this.count == 0) { diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch new file mode 100644 index 0000000..8356090 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -300,6 +_,7 @@ + public volatile boolean abnormalExit; // Paper - Improved watchdog support + public volatile Thread shutdownThread; // Paper - Improved watchdog support + public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files ++ public final me.samsuik.sakura.configuration.SakuraConfigurations sakuraConfigurations; // Sakura + public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked + private final Set pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping + public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation +@@ -390,6 +_,17 @@ + } + } + // Paper end - rewrite chunk system ++ // Sakura start - track tick information ++ private final me.samsuik.sakura.tps.TickInformationCollector tickInformationCollector = new me.samsuik.sakura.tps.TickInformationCollector(); ++ ++ public final me.samsuik.sakura.tps.ServerTickInformation latestTickInformation() { ++ return this.tickInformationCollector.latestTickInformation(); ++ } ++ ++ public final ImmutableList tickHistory(long from, long to) { ++ return this.tickInformationCollector.collect(from, to); ++ } ++ // Sakura end - track tick information + + public MinecraftServer( + // CraftBukkit start +@@ -471,6 +_,10 @@ + Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this)); + // CraftBukkit end + this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files ++ // Sakura start ++ final java.nio.file.Path sakuraConfigDirPath = ((java.io.File) options.valueOf("sakura-settings-directory")).toPath(); ++ this.sakuraConfigurations = me.samsuik.sakura.configuration.SakuraConfigurations.setup(sakuraConfigDirPath); ++ // Sakura end + } + + private void readScoreboard(DimensionDataStorage dataStorage) { +@@ -1221,6 +_,7 @@ + if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) { + final long diff = currentTime - tickSection; + final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP); ++ this.tickInformationCollector.levelData(this.levels.values(), currentTps.doubleValue()); // Sakura - track tick information + tps1.add(currentTps, diff); + tps5.add(currentTps, diff); + tps15.add(currentTps, diff); +@@ -1256,6 +_,7 @@ + throw new RuntimeException("Chunk system crash propagated to tick()", crash); + } + // Paper end - rewrite chunk system ++ this.tickInformationCollector.tickDuration((System.nanoTime() - currentTime) / 1_000_000L); // Sakura - track tick information + this.tickFrame.end(); + profilerFiller.popPush("nextTickWait"); + this.mayHaveDelayedTasks = true; diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch new file mode 100644 index 0000000..305fa53 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -225,6 +_,11 @@ + this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now ++ // Sakura start - sakura configuration files ++ sakuraConfigurations.initializeGlobalConfiguration(this.registryAccess()); ++ sakuraConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); ++ me.samsuik.sakura.command.SakuraCommands.registerCommands(this); ++ // Sakura end - sakura configuration files + + this.setPvpAllowed(properties.pvp); + this.setFlightAllowed(properties.allowFlight); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch new file mode 100644 index 0000000..5ba9156 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -129,7 +_,7 @@ + public final AtomicInteger tickingGenerated = new AtomicInteger(); // Paper - public + private final String storageName; + private final PlayerMap playerMap = new PlayerMap(); +- public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); ++ public final Int2ObjectMap entityMap = new me.samsuik.sakura.utils.collections.TrackedEntityChunkMap(); // Sakura - optimised tracked entity map + private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); + // Paper - rewrite chunk system + public int serverViewDistance; +@@ -1217,7 +_,10 @@ + double vec3_dz = player.getZ() - this.entity.getZ(); + // Paper end - remove allocation of Vec3D here + int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player); +- double d = Math.min(this.getEffectiveRange(), playerViewDistance * 16); ++ // Sakura start - entity tracking range modifier ++ double visibleRange = this.getEffectiveRange() * player.trackingRangeModifier; ++ double d = Math.min(visibleRange, playerViewDistance * 16); ++ // Sakura end - entity tracking range modifier + double d1 = vec3_dx * vec3_dx + vec3_dz * vec3_dz; // Paper + double d2 = d * d; + // Paper start - Configurable entity tracking range by Y diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch new file mode 100644 index 0000000..33776a2 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -588,7 +_,7 @@ + org.bukkit.generator.BiomeProvider biomeProvider // CraftBukkit + ) { + // CraftBukkit start +- 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 ++ 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 + this.pvpMode = server.isPvpAllowed(); + this.levelStorageAccess = levelStorageAccess; + this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile()); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch new file mode 100644 index 0000000..d06d034 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -423,6 +_,7 @@ + return this.viewDistanceHolder; + } + // Paper end - rewrite chunk system ++ public double trackingRangeModifier = 1.0; // Sakura - entity tracking range modifier + + public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { + super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch new file mode 100644 index 0000000..5f5569a --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -525,6 +_,7 @@ + } + } + // Paper end - optimise entity tracker ++ public boolean pushedByFluid = true; // Sakura - entity pushed by fluid api + + public Entity(EntityType entityType, Level level) { + this.type = entityType; +@@ -4025,7 +_,7 @@ + } + + public boolean isPushedByFluid() { +- return true; ++ return this.pushedByFluid; // Sakura - entity pushed by fluid api + } + + public static double getViewScale() { diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch new file mode 100644 index 0000000..6e55596 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -0,0 +1,176 @@ +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -307,6 +_,43 @@ + return this.getYHeadRot(); + } + // CraftBukkit end ++ // Sakura start - legacy combat mechanics ++ private static final ResourceLocation LEGACY_COMBAT_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("sakura", "legacy_combat"); ++ private static final AttributeModifier LEGACY_ATTACK_SPEED_MODIFIER = new AttributeModifier(LEGACY_COMBAT_MODIFIER_ID, 100.0, AttributeModifier.Operation.ADD_VALUE); ++ ++ private void updateAttackSpeedModifier() { ++ AttributeInstance attackSpeed = this.getAttribute(Attributes.ATTACK_SPEED); ++ if (attackSpeed != null) { ++ attackSpeed.removeModifier(LEGACY_ATTACK_SPEED_MODIFIER); ++ ++ if (this.level().sakuraConfig().players.combat.legacyCombatMechanics) { ++ attackSpeed.addTransientModifier(LEGACY_ATTACK_SPEED_MODIFIER); ++ } ++ } ++ } ++ ++ protected final float getAttackDamageFromAttributes() { ++ AttributeInstance attackDamage = this.getAttribute(Attributes.ATTACK_DAMAGE); ++ AttributeModifier legacyModifier = null; ++ ++ if (this.level().sakuraConfig().players.combat.legacyCombatMechanics) { ++ ItemStack heldItem = this.getLastHandItem(EquipmentSlot.MAINHAND); ++ double attackDifference = me.samsuik.sakura.player.combat.CombatUtil.getLegacyAttackDifference(heldItem); ++ legacyModifier = new AttributeModifier(LEGACY_COMBAT_MODIFIER_ID, attackDifference, AttributeModifier.Operation.ADD_VALUE); ++ } ++ ++ final double damage; ++ if (attackDamage == null || legacyModifier == null) { ++ damage = this.getAttributeValue(Attributes.ATTACK_DAMAGE); ++ } else { ++ attackDamage.addTransientModifier(legacyModifier); ++ damage = this.getAttributeValue(Attributes.ATTACK_DAMAGE); ++ attackDamage.removeModifier(legacyModifier); ++ } ++ ++ return (float) damage; ++ } ++ // Sakura end - legacy combat mechanics + + protected LivingEntity(EntityType entityType, Level level) { + super(entityType, level); +@@ -1479,7 +_,7 @@ + } + // Paper end - Check distance in entity interactions + +- this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events ++ this.knockback((float) this.level().sakuraConfig().players.knockback.baseKnockback, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events // Sakura - configure entity knockback + if (!flag) { + this.indicateDamage(d, d1); + } +@@ -1570,7 +_,7 @@ + } + + protected void blockedByShield(LivingEntity defender) { +- defender.knockback(0.5, defender.getX() - this.getX(), defender.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events ++ defender.knockback((float) this.level().sakuraConfig().players.knockback.shieldHitKnockback, defender.getX() - this.getX(), defender.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events // Sakura - configure entity knockback + } + + private boolean checkTotemDeathProtection(DamageSource damageSource) { +@@ -1747,6 +_,12 @@ + + // Paper start + if (this.dead) { // Paper ++ // Sakura start - instant mob death animation ++ if (this.level().sakuraConfig().entity.instantDeathAnimation && !(this instanceof Player)) { ++ this.deathTime = 20; ++ return; ++ } ++ // Sakura end - instant mob death animation + this.level().broadcastEntityEvent(this, (byte)3); + + this.setPose(Pose.DYING); +@@ -1920,7 +_,7 @@ + } + + public void knockback(double strength, double x, double z, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause eventCause) { // Paper - knockback events +- strength *= 1.0 - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); ++ strength *= 1.0 - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE) * this.level().sakuraConfig().players.knockback.knockbackResistanceModifier; // Sakura - configure entity knockback + if (true || !(strength <= 0.0)) { // CraftBukkit - Call event even when force is 0 + // this.hasImpulse = true; // CraftBukkit - Move down + Vec3 deltaMovement = this.getDeltaMovement(); +@@ -1931,10 +_,18 @@ + } + + Vec3 vec3 = new Vec3(x, 0.0, z).normalize().scale(strength); ++ // Sakura start - configure entity knockback ++ double velocityY = deltaMovement.y / 2.0D + this.level().sakuraConfig().players.knockback.knockbackVertical.or(strength); ++ if (!this.level().sakuraConfig().players.knockback.verticalKnockbackRequireGround || this.onGround()) { ++ velocityY = Math.min(this.level().sakuraConfig().players.knockback.knockbackVerticalLimit, velocityY); ++ } else { ++ velocityY = deltaMovement.y; ++ } ++ // Sakura end - configure entity knockback + // Paper start - knockback events + Vec3 finalVelocity = new Vec3( + deltaMovement.x / 2.0 - vec3.x, +- this.onGround() ? Math.min(0.4, deltaMovement.y / 2.0 + strength) : deltaMovement.y, ++ velocityY, // Sakura - configure entity knockback + deltaMovement.z / 2.0 - vec3.z + ); + Vec3 diff = finalVelocity.subtract(deltaMovement); +@@ -2156,9 +_,21 @@ + protected float getDamageAfterArmorAbsorb(DamageSource damageSource, float damageAmount) { + if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) { + // this.hurtArmor(damageSource, damageAmount); // CraftBukkit - actuallyHurt(DamageSource, float, EntityDamageEvent) for damage handling ++ // Sakura start - legacy combat mechanics ++ if (!this.level().sakuraConfig().players.combat.legacyCombatMechanics) { + damageAmount = CombatRules.getDamageAfterAbsorb( + this, damageAmount, damageSource, this.getArmorValue(), (float)this.getAttributeValue(Attributes.ARMOR_TOUGHNESS) + ); ++ } else { ++ // See: applyArmorModifier(DamageSource, float) ++ // int k = 1.0 - (20.0 / 25.0); ++ // int i = 25 - this.getArmorValue(); ++ // float f1 = damageAmount * (float) i; ++ // damageAmount = f1 / 25.0F; ++ float armorDamageModifier = 1.0f - (this.getArmorValue() / 25.0f); ++ damageAmount *= armorDamageModifier; ++ } ++ // Sakura end - legacy combat mechanics + } + + return damageAmount; +@@ -2248,7 +_,13 @@ + com.google.common.base.Function blocking = new com.google.common.base.Function() { + @Override + public Double apply(Double f) { ++ // Sakura start - shield damage reduction ++ if (!level().sakuraConfig().players.combat.shieldDamageReduction || damagesource.getDirectEntity() instanceof AbstractArrow) { + return -((LivingEntity.this.isDamageSourceBlocked(damagesource)) ? f : 0.0); ++ } else { ++ return -(LivingEntity.this.isBlocking() ? f * 0.5 : 0.0); ++ } ++ // Sakura end - shield damage reduction + } + }; + float blockingModifier = blocking.apply((double) amount).floatValue(); +@@ -2344,6 +_,12 @@ + // Apply damage to armor + if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) { + float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT)); ++ // Sakura start - add max armour durability damage ++ final int maxArmourDamage = this.level().sakuraConfig().players.combat.maxArmourDamage.or(-1); ++ if (maxArmourDamage >= 0) { ++ armorDamage = Math.min(armorDamage, maxArmourDamage); ++ } ++ // Sakura end - add max armour durability damage + this.hurtArmor(damageSource, armorDamage); + } + +@@ -3269,6 +_,11 @@ + if (this.level() instanceof ServerLevel serverLevel) { + EnchantmentHelper.runLocationChangedEffects(serverLevel, itemBySlot, this, equipmentSlot1); + } ++ // Sakura start - legacy combat mechanics ++ if (this instanceof ServerPlayer && equipmentSlot1 == EquipmentSlot.MAINHAND) { ++ this.updateAttackSpeedModifier(); ++ } ++ // Sakura end - legacy combat mechanics + } + } + } +@@ -3492,7 +_,7 @@ + } + } + // Paper end - Add EntityMoveEvent +- if (this.level() instanceof ServerLevel serverLevel && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { ++ if (this.level() instanceof ServerLevel serverLevel && this.level().sakuraConfig().entity.waterSensitivity && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { // Sakura - configure entity water sensitivity + this.hurtServer(serverLevel, this.damageSources().drown(), 1.0F); + } + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch new file mode 100644 index 0000000..71ea142 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -846,7 +_,7 @@ + protected final void serverAiStep() { + this.noActionTime++; + // Paper start - Allow nerfed mobs to jump and float +- if (!this.aware) { ++ if (!this.aware || this.level().sakuraConfig().entity.disableMobAi) { // Sakura - add option to disable entity ai + if (goalFloat != null) { + if (goalFloat.canUse()) goalFloat.tick(); + this.getJumpControl().tick(); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch new file mode 100644 index 0000000..1521ab5 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/entity/animal/IronGolem.java ++++ b/net/minecraft/world/entity/animal/IronGolem.java +@@ -228,6 +_,19 @@ + } + } + ++ // Sakura start - configure iron golems taking fall damage ++ @Override ++ protected int calculateFallDamage(float fallDistance, float damageMultiplier) { ++ if (!this.level().sakuraConfig().entity.ironGolemsTakeFalldamage) { ++ return super.calculateFallDamage(fallDistance, damageMultiplier); ++ } else { ++ float safeFallDistance = (float)this.getAttributeValue(Attributes.SAFE_FALL_DISTANCE); ++ float damage = fallDistance - safeFallDistance; ++ return net.minecraft.util.Mth.ceil(damage * damageMultiplier * this.getAttributeValue(Attributes.FALL_DAMAGE_MULTIPLIER)); ++ } ++ } ++ // Sakura end - configure iron golems taking fall damage ++ + public int getAttackAnimationTick() { + return this.attackAnimationTick; + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch new file mode 100644 index 0000000..a4dcaa4 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch @@ -0,0 +1,66 @@ +--- a/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -68,9 +_,11 @@ + public boolean forceTickAfterTeleportToDuplicate; + protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); + public boolean autoExpire = true; // Paper - Expand FallingBlock API ++ public boolean heightParity; // Sakura - falling block height parity api + + public FallingBlockEntity(EntityType entityType, Level level) { + super(entityType, level); ++ this.heightParity = level.sakuraConfig().cannons.mechanics.fallingBlockParity; // Sakura - configure cannon mechanics + } + + public FallingBlockEntity(Level level, double x, double y, double z, BlockState state) { +@@ -138,6 +_,13 @@ + return !this.isRemoved(); + } + ++ // Sakura start - falling block height parity api ++ @Override ++ public final double getEyeY() { ++ return this.heightParity ? this.getY() : super.getEyeY(); ++ } ++ // Sakura end - falling block height parity api ++ + @Override + protected double getDefaultGravity() { + return 0.04; +@@ -165,7 +_,7 @@ + this.handlePortal(); + if (this.level() instanceof ServerLevel serverLevel && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) { + BlockPos blockPos = this.blockPosition(); +- boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock; ++ boolean flag = this.level().sakuraConfig().cannons.sand.concreteSolidifyInWater && this.blockState.getBlock() instanceof ConcretePowderBlock; // Sakura - configure concrete solidifying in water + boolean flag1 = flag && this.level().getFluidState(blockPos).is(FluidTags.WATER); + double d = this.getDeltaMovement().lengthSqr(); + if (flag && d > 1.0) { +@@ -181,7 +_,7 @@ + } + } + +- if (!this.onGround() && !flag1) { ++ if (!this.onGround() && !flag1 || this.level().sakuraConfig().cannons.sand.despawnInsideMovingPistons && this.autoExpire && this.time > 600) { // Sakura - allow falling blocks to despawn inside moving pistons + if ((this.time > 100 && autoExpire) && (blockPos.getY() <= this.level().getMinY() || blockPos.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API + if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { + this.spawnAtLocation(serverLevel, block); +@@ -199,7 +_,7 @@ + ); + boolean flag2 = FallingBlock.isFree(this.level().getBlockState(blockPos.below())) && (!flag || !flag1); + boolean flag3 = this.blockState.canSurvive(this.level(), blockPos) && !flag2; +- if (canBeReplaced && flag3) { ++ if (canBeReplaced && flag3 && this.level().sakuraConfig().cannons.sand.isFallingBlockInBounds(this)) { // Sakura - falling block stacking restrictions + if (this.blockState.hasProperty(BlockStateProperties.WATERLOGGED) + && this.level().getFluidState(blockPos).getType() == Fluids.WATER) { + this.blockState = this.blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(true)); +@@ -243,6 +_,10 @@ + this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause + this.callOnBrokenAfterFall(block, blockPos); + this.spawnAtLocation(serverLevel, block); ++ // Sakura start - fix falling blocks staying alive when entity drops are disabled ++ } else { ++ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause ++ // Sakura end - fix falling blocks staying alive when entity drops are disabled + } + } else { + this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch new file mode 100644 index 0000000..c2776e9 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -363,6 +_,11 @@ + + @Override + public boolean ignoreExplosion(Explosion explosion) { ++ // Sakura start - add list of items that ignore explosions ++ if (this.level().sakuraConfig().entity.items.explosionResistantItems.contains(this.getItem().getItem())) { ++ return true; ++ } ++ // Sakura end - add list of items that ignore explosions + return !explosion.shouldAffectBlocklikeEntities() || super.ignoreExplosion(explosion); + } + diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch new file mode 100644 index 0000000..b4afd5c --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch @@ -0,0 +1,70 @@ +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -73,6 +_,12 @@ + this.yo = y; + this.zo = z; + this.owner = owner; ++ // Sakura start - configure cannon mechanics ++ switch (level.sakuraConfig().cannons.mechanics.tntSpread) { ++ case NONE -> this.setDeltaMovement(0.0, 0.0, 0.0); ++ case Y -> this.setDeltaMovement(this.getDeltaMovement().multiply(0.0, 1.0, 0.0)); ++ } ++ // Sakura end - configure cannon mechanics + } + + @Override +@@ -91,6 +_,21 @@ + return !this.isRemoved(); + } + ++ // Sakura start - optimise tnt fluid state ++ @Override ++ protected boolean updateInWaterStateAndDoFluidPushing() { ++ if (this.isPushedByFluid()) { ++ return super.updateInWaterStateAndDoFluidPushing(); ++ } else { ++ // super method also handles lava fluid pushing ++ // we only need to search for water to negate fall distance ++ this.fluidHeight.clear(); ++ this.updateInWaterStateAndDoWaterCurrentPushing(); ++ return this.isInWater(); ++ } ++ } ++ // Sakura end - optimise tnt fluid state ++ + @Override + protected double getDefaultGravity() { + return 0.04; +@@ -98,7 +_,7 @@ + + @Override + public void tick() { +- if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot ++ // Sakura - remove max tnt per tick + this.handlePortal(); + this.applyGravity(); + this.move(MoverType.SELF, this.getDeltaMovement()); +@@ -130,6 +_,14 @@ + this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5, this.getZ(), 0.0, 0.0, 0.0); + } + } ++ // Sakura start - configure force position updates ++ if (this.level().sakuraConfig().cannons.tnt.forcePositionUpdates) { ++ this.forcePositionUpdateInWater(); ++ } ++ } ++ ++ private void forcePositionUpdateInWater() { ++ // Sakura end - configure force position updates + // Paper start - Option to prevent TNT from moving in water + if (!this.isRemoved() && this.wasTouchingWater && this.level().paperConfig().fixes.preventTntFromMovingInWater) { + /* +@@ -248,7 +_,7 @@ + // Paper start - Option to prevent TNT from moving in water + @Override + public boolean isPushedByFluid() { +- return !this.level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); ++ return !this.level().paperConfig().fixes.preventTntFromMovingInWater && this.level().sakuraConfig().cannons.mechanics.tntFlowsInWater && super.isPushedByFluid(); // Sakura - configure cannon mechanics + } + // Paper end - Option to prevent TNT from moving in water + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch new file mode 100644 index 0000000..6066b1d --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch @@ -0,0 +1,133 @@ +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -200,6 +_,7 @@ + private int currentImpulseContextResetGraceTime; + public boolean affectsSpawning = true; // Paper - Affects Spawning API + public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage ++ private long lastSprintKnockback = -1; // Sakura - configure entity knockback + + // CraftBukkit start + public boolean fauxSleeping; +@@ -895,6 +_,10 @@ + public boolean isInvulnerableTo(ServerLevel level, DamageSource damageSource) { + if (super.isInvulnerableTo(level, damageSource)) { + return true; ++ // Sakura start - allow disabling explosions hurting players ++ } else if (!this.level().sakuraConfig().cannons.explosion.explosionsHurtPlayers && damageSource.is(DamageTypeTags.IS_EXPLOSION)) { ++ return true; ++ // Sakura end - allow disabling explosions hurting players + } else if (damageSource.is(DamageTypeTags.IS_DROWNING)) { + return !level.getGameRules().getBoolean(GameRules.RULE_DROWNING_DAMAGE); + } else if (damageSource.is(DamageTypeTags.IS_FALL)) { +@@ -1225,13 +_,19 @@ + if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. + { + // Paper end - PlayerAttackEntityEvent +- float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE); ++ float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : this.getAttackDamageFromAttributes(); // Sakura - legacy combat mechanics + ItemStack weaponItem = this.getWeaponItem(); + DamageSource damageSource = Optional.ofNullable(weaponItem.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this)); + float f1 = this.getEnchantedDamage(target, f, damageSource) - f; + float attackStrengthScale = this.getAttackStrengthScale(0.5F); ++ // Sakura start - legacy combat mechanics ++ if (!this.level().sakuraConfig().players.combat.legacyCombatMechanics) { + f *= 0.2F + attackStrengthScale * attackStrengthScale * 0.8F; + f1 *= attackStrengthScale; ++ } else if (f1 != 0.0) { ++ f1 += me.samsuik.sakura.player.combat.CombatUtil.calculateLegacySharpnessDamage(this, weaponItem, damageSource); ++ } ++ // Sakura end - legacy combat mechanics + // this.resetAttackStrengthTicker(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt + if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) + && target instanceof Projectile projectile) { +@@ -1249,7 +_,7 @@ + if (f > 0.0F || f1 > 0.0F) { + boolean flag = attackStrengthScale > 0.9F; + boolean flag1; +- if (this.isSprinting() && flag) { ++ if (this.isSprinting() && (!this.level().sakuraConfig().players.knockback.sprinting.requireFullAttack || flag)) { // Sakura - configure entity knockback + this.sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + flag1 = true; + } else { +@@ -1265,7 +_,7 @@ + && !this.hasEffect(MobEffects.BLINDNESS) + && !this.isPassenger() + && target instanceof LivingEntity +- && !this.isSprinting(); ++ && (this.level().sakuraConfig().players.combat.legacyCombatMechanics || !this.isSprinting()); // Sakura - legacy combat mechanics + flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits + if (flag2) { + damageSource = damageSource.critical(true); // Paper start - critical damage API +@@ -1292,7 +_,21 @@ + if (flag4) { + float f4 = this.getKnockback(target, damageSource) + (flag1 ? 1.0F : 0.0F); + if (f4 > 0.0F) { +- if (target instanceof LivingEntity livingEntity1) { ++ // Sakura start - configure entity knockback; extra sprinting knockback ++ long millis = System.currentTimeMillis(); ++ long sinceLastKnockback = millis - this.lastSprintKnockback; ++ if (flag1) { // attackHasExtraKnockback ++ double knockbackToApply = 0.0; ++ if (sinceLastKnockback >= this.level().sakuraConfig().players.knockback.sprinting.knockbackDelay.value().orElse(0)) { ++ knockbackToApply = this.level().sakuraConfig().players.knockback.sprinting.extraKnockback; ++ this.lastSprintKnockback = millis; ++ } ++ f4 = (f4 - 1.0f) + ((float) knockbackToApply * 2.0f); ++ } ++ if (f4 == 0.0f) { ++ // required ++ } else if (target instanceof LivingEntity livingEntity1) { ++ // Sakura end - configure entity knockback; extra sprinting knockback + livingEntity1.knockback( + f4 * 0.5F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) + , this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK // Paper - knockback events +@@ -1314,7 +_,7 @@ + // Paper end - Configurable sprint interruption on attack + } + +- if (flag3) { ++ if (flag3 && this.level().sakuraConfig().players.combat.allowSweepAttacks) { // Sakura - allow disabling sweep attacks + float f5 = 1.0F + (float)this.getAttributeValue(Attributes.SWEEPING_DAMAGE_RATIO) * f; + + for (LivingEntity livingEntity2 : this.level() +@@ -1331,7 +_,7 @@ + } + // CraftBukkit end + livingEntity2.knockback( +- 0.4F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) ++ (float) this.level().sakuraConfig().players.knockback.sweepingEdgeKnockback, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) // Sakura - configure entity knockback + , this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK // CraftBukkit // Paper - knockback events + ); + // CraftBukkit - moved up +@@ -1421,7 +_,7 @@ + if (target instanceof LivingEntity) { + float f7 = f3 - ((LivingEntity)target).getHealth(); + this.awardStat(Stats.DAMAGE_DEALT, Math.round(f7 * 10.0F)); +- if (this.level() instanceof ServerLevel && f7 > 2.0F) { ++ if (this.level() instanceof ServerLevel && f7 > 2.0F && !this.level().sakuraConfig().players.combat.oldSoundsAndParticleEffects) { // Sakura - old combat sounds and particles + int i = (int)(f7 * 0.5); + ((ServerLevel)this.level()) + .sendParticles(ParticleTypes.DAMAGE_INDICATOR, target.getX(), target.getY(0.5), target.getZ(), i, 0.1, 0.0, 0.1, 0.2); +@@ -1824,6 +_,7 @@ + + // Paper start - send while respecting visibility + private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { ++ if (fromEntity.level().sakuraConfig().players.combat.oldSoundsAndParticleEffects) return; // Sakura - old combat sounds and particles + fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity itself + if (fromEntity instanceof ServerPlayer serverPlayer) { + serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong())); +@@ -2210,7 +_,13 @@ + + @Override + public EntityDimensions getDefaultDimensions(Pose pose) { +- return POSES.getOrDefault(pose, STANDING_DIMENSIONS); ++ // Sakura start - player poses shrink collision box ++ final EntityDimensions dimensions = POSES.getOrDefault(pose, STANDING_DIMENSIONS); ++ if (!this.level().sakuraConfig().players.posesShrinkCollisionBox && dimensions.height() == STANDING_DIMENSIONS.height()) { ++ return STANDING_DIMENSIONS; ++ } ++ return dimensions; ++ // Sakura end - player poses shrink collision box + } + + @Override diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch new file mode 100644 index 0000000..c8cfb32 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/net/minecraft/world/entity/projectile/FishingHook.java +@@ -288,6 +_,12 @@ + if (!this.level().isClientSide) { + this.setHookedEntity(result.getEntity()); + } ++ // Sakura start - configure entity knockback ++ if (this.level().sakuraConfig().players.knockback.fishingHooksApplyKnockback) { ++ final Entity entity = result.getEntity(); ++ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0f); ++ } ++ // Sakura end - configure entity knockback + } + + @Override +@@ -603,7 +_,7 @@ + + public void pullEntity(Entity entity) { + Entity owner = this.getOwner(); +- if (owner != null) { ++ if (owner != null && (this.level().sakuraConfig().players.fishingHooksPullEntities || entity instanceof ItemEntity)) { // Sakura - configure fishing hooks pulling entities + Vec3 vec3 = new Vec3(owner.getX() - this.getX(), owner.getY() - this.getY(), owner.getZ() - this.getZ()).scale(0.1); + entity.setDeltaMovement(entity.getDeltaMovement().add(vec3)); + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ProjectileUtil.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ProjectileUtil.java.patch new file mode 100644 index 0000000..b44a049 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ProjectileUtil.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/entity/projectile/ProjectileUtil.java ++++ b/net/minecraft/world/entity/projectile/ProjectileUtil.java +@@ -51,9 +_,15 @@ + vec3 = hitResult.getLocation(); + } + +- HitResult entityHitResult = getEntityHitResult( +- level, projectile, pos, vec3, projectile.getBoundingBox().expandTowards(deltaMovement).inflate(1.0), filter, margin +- ); ++ // Sakura start - configure potion mechanics ++ final AABB movementAABB = projectile.getBoundingBox().expandTowards(deltaMovement).inflate(1.0); ++ final HitResult entityHitResult; ++ if (level.sakuraConfig().entity.thrownPotion.allowBreakingInsideEntities && projectile instanceof ThrownPotion) { ++ entityHitResult = getEntityHitResult(projectile, pos, vec3, movementAABB, filter, margin); ++ } else { ++ entityHitResult = getEntityHitResult(level, projectile, pos, vec3, movementAABB, filter, margin); ++ } ++ // Sakura end - configure potion mechanics + if (entityHitResult != null) { + hitResult = entityHitResult; + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch new file mode 100644 index 0000000..e6501d5 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java ++++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java +@@ -41,12 +_,18 @@ + return true; + } + ++ // Sakura start - enderpearls use outline for collision ++ protected net.minecraft.world.level.ClipContext.Block getClipType() { ++ return net.minecraft.world.level.ClipContext.Block.COLLIDER; ++ } ++ // Sakura end - enderpearls use outline for collision ++ + @Override + public void tick() { + this.handleFirstTickBubbleColumn(); + this.applyGravity(); + this.applyInertia(); +- HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); ++ HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType()); // Sakura - enderpearls use outline for collision + Vec3 location; + if (hitResultOnMoveVector.getType() != HitResult.Type.MISS) { + location = hitResultOnMoveVector.getLocation(); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch new file mode 100644 index 0000000..611cfaf --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java ++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +@@ -186,6 +_,13 @@ + } + } + ++ // Sakura start - enderpearls use outline for collision ++ @Override ++ protected net.minecraft.world.level.ClipContext.Block getClipType() { ++ return this.level().sakuraConfig().entity.enderPearl.useOutlineForCollision ? net.minecraft.world.level.ClipContext.Block.OUTLINE : super.getClipType(); ++ } ++ // Sakura end - enderpearls use outline for collision ++ + @Override + public void tick() { + int sectionPosX = SectionPos.blockToSectionCoord(this.position().x()); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch new file mode 100644 index 0000000..6857438 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -49,6 +_,25 @@ + public ThrownPotion(Level level, double x, double y, double z, ItemStack item) { + super(EntityType.POTION, x, y, z, level, item); + } ++ ++ // Sakura start - configure potion mechanics ++ @Override ++ public void shoot(double x, double y, double z, float speed, float divergence) { ++ super.shoot(x, y, z, speed, divergence); ++ ++ net.minecraft.world.phys.Vec3 movement = this.getDeltaMovement(); ++ double moveX = movement.x * this.level().sakuraConfig().entity.thrownPotion.horizontalSpeed; ++ double moveY = movement.y * this.level().sakuraConfig().entity.thrownPotion.verticalSpeed; ++ double moveZ = movement.z * this.level().sakuraConfig().entity.thrownPotion.horizontalSpeed; ++ ++ this.setDeltaMovement(moveX, moveY, moveZ); ++ } ++ ++ @Override ++ protected boolean checkLeftOwner() { ++ return super.checkLeftOwner() || this.level().sakuraConfig().entity.thrownPotion.allowBreakingInsideEntities && this.tickCount >= 5; ++ } ++ // Sakura end - configure potion mechanics + + @Override + protected Item getDefaultItem() { diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/food/FoodData.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/food/FoodData.java.patch new file mode 100644 index 0000000..617b9ba --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/food/FoodData.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/food/FoodData.java ++++ b/net/minecraft/world/food/FoodData.java +@@ -63,7 +_,7 @@ + } + + boolean _boolean = serverLevel.getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION); +- if (_boolean && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) { ++ if (_boolean && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20 && player.level().sakuraConfig().players.combat.fastHealthRegen) { // Sakura - configure fast health regen + this.tickTimer++; + if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit + float min = Math.min(this.saturationLevel, 6.0F); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/item/BucketItem.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/item/BucketItem.java.patch new file mode 100644 index 0000000..0f51f96 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/item/BucketItem.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/item/BucketItem.java ++++ b/net/minecraft/world/item/BucketItem.java +@@ -112,6 +_,17 @@ + return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : bucketStack; + } + ++ // Sakura start - modify bucket stack size ++ @Override ++ public void verifyComponentsAfterLoad(ItemStack stack) { ++ me.samsuik.sakura.configuration.GlobalConfiguration config = me.samsuik.sakura.configuration.GlobalConfiguration.get(); ++ int customItemSize = config == null || !config.players.bucketStackSize.isDefined() ? -1 : config.players.bucketStackSize.intValue(); ++ if (customItemSize > 0 && customItemSize < 100) { ++ stack.set(net.minecraft.core.component.DataComponents.MAX_STACK_SIZE, customItemSize); ++ } ++ } ++ // Sakura end - modify bucket stack size ++ + @Override + public void checkExtraContent(@Nullable Player player, Level level, ItemStack containerStack, BlockPos pos) { + } +@@ -147,7 +_,7 @@ + // CraftBukkit end + if (!flag) { + return result != null && this.emptyContents(player, level, result.getBlockPos().relative(result.getDirection()), null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit +- } else if (level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { ++ } else if (!level.sakuraConfig().environment.allowWaterInTheNether && level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { // Sakura - allow water in the nether + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/item/Items.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/item/Items.java.patch new file mode 100644 index 0000000..11e6040 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/item/Items.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/item/Items.java ++++ b/net/minecraft/world/item/Items.java +@@ -1194,6 +_,7 @@ + public static final Item GOLDEN_APPLE = registerItem("golden_apple", new Item.Properties().food(Foods.GOLDEN_APPLE, Consumables.GOLDEN_APPLE)); + public static final Item ENCHANTED_GOLDEN_APPLE = registerItem( + "enchanted_golden_apple", ++ me.samsuik.sakura.player.combat.CustomGoldenApple::new, // Sakura - old enchanted golden apples + new Item.Properties() + .rarity(Rarity.RARE) + .food(Foods.ENCHANTED_GOLDEN_APPLE, Consumables.ENCHANTED_GOLDEN_APPLE) diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/BaseSpawner.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/BaseSpawner.java.patch new file mode 100644 index 0000000..8ea169e --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/BaseSpawner.java.patch @@ -0,0 +1,76 @@ +--- a/net/minecraft/world/level/BaseSpawner.java ++++ b/net/minecraft/world/level/BaseSpawner.java +@@ -46,12 +_,24 @@ + public int spawnRange = 4; + private int tickDelay = 0; // Paper - Configurable mob spawner tick rate + ++ // Sakura start - configure default mob spawner properties ++ public BaseSpawner() { ++ this.minSpawnDelay = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.minSpawnDelay; ++ this.maxSpawnDelay = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.maxSpawnDelay; ++ this.spawnCount = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.spawnCount; ++ this.maxNearbyEntities = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.maxNearbyEntities; ++ this.requiredPlayerRange = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.requiredPlayerRange; ++ this.spawnRange = me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.mobSpawnerDefaults.spawnRange; ++ } ++ // Sakura start - configure default mob spawner properties ++ + public void setEntityId(EntityType type, @Nullable Level level, RandomSource random, BlockPos pos) { + this.getOrCreateNextSpawnData(level, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString()); + this.spawnPotentials = SimpleWeightedRandomList.empty(); // CraftBukkit - SPIGOT-3496, MC-92282 + } + + public boolean isNearPlayer(Level level, BlockPos pos) { ++ if (!level.sakuraConfig().environment.mobSpawner.requireNearbyPlayer) return true; // Sakura - configure mob spawner behaviour + return level.hasNearbyAlivePlayerThatAffectsSpawning(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API + } + +@@ -117,7 +_,7 @@ + if (!customSpawnRules.isValidPosition(blockPos, serverLevel)) { + continue; + } +- } else if (!SpawnPlacements.checkSpawnRules(optional.get(), serverLevel, EntitySpawnReason.SPAWNER, blockPos, serverLevel.getRandom())) { ++ } else if (serverLevel.sakuraConfig().environment.mobSpawner.checkSpawnConditions && !SpawnPlacements.checkSpawnRules(optional.get(), serverLevel, EntitySpawnReason.SPAWNER, blockPos, serverLevel.getRandom())) { // Sakura - configure mob spawner behaviour + continue; + } + +@@ -145,12 +_,19 @@ + return; + } + +- int size1 = serverLevel.getEntities( +- EntityTypeTest.forExactClass(entity.getClass()), +- new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1).inflate(this.spawnRange), +- EntitySelector.NO_SPECTATORS +- ) +- .size(); ++ // Sakura start - configure mob spawner behaviour ++ final int size1; ++ if (serverLevel.sakuraConfig().environment.mobSpawner.ignoreEntityLimit) { ++ size1 = 0; ++ } else { ++ size1 = serverLevel.getEntities( ++ EntityTypeTest.forExactClass(entity.getClass()), ++ new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1).inflate(this.spawnRange), ++ EntitySelector.NO_SPECTATORS ++ ) ++ .size(); ++ } ++ // Sakura end - configure mob spawner behaviour + if (size1 >= this.maxNearbyEntities) { + this.delay(serverLevel, pos); + return; +@@ -159,8 +_,11 @@ + entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag + entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), random.nextFloat() * 360.0F, 0.0F); + if (entity instanceof Mob mob) { +- if (nextSpawnData.getCustomSpawnRules().isEmpty() && !mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER) +- || !mob.checkSpawnObstruction(serverLevel)) { ++ // Sakura start - configure mob spawner behaviour ++ if (serverLevel.sakuraConfig().environment.mobSpawner.checkSpawnConditions && ++ (nextSpawnData.getCustomSpawnRules().isEmpty() && !mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER) ++ || !mob.checkSpawnObstruction(serverLevel))) { ++ // Sakura end - configure mob spawner behaviour + continue; + } + diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch new file mode 100644 index 0000000..24063a8 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -168,6 +_,18 @@ + return this.paperConfig; + } + // Paper end - add paper world config ++ // Sakura start - sakura configuration files ++ private final me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig; ++ public final me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig() { ++ return this.sakuraConfig; ++ } ++ // Sakura end - sakura configuration files ++ // Sakura start - local config and property storage ++ private final me.samsuik.sakura.local.config.LocalConfigManager localConfig = new me.samsuik.sakura.local.config.LocalConfigManager(this); ++ public final me.samsuik.sakura.local.config.LocalConfigManager localConfig() { ++ return this.localConfig; ++ } ++ // Sakura end - local config and property storage + + public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public static BlockPos lastPhysicsProblem; // Spigot +@@ -840,6 +_,7 @@ + org.bukkit.World.Environment env, // CraftBukkit + java.util.function.Function paperWorldConfigCreator, // Paper - create paper world config ++ java.util.function.Supplier sakuraWorldConfigCreator, // Sakura - sakura configuration files + java.util.concurrent.Executor executor // Paper - Anti-Xray + ) { + // Paper start - getblock optimisations - cache world height/sections +@@ -853,6 +_,7 @@ + // Paper end - getblock optimisations - cache world height/sections + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config ++ this.sakuraConfig = sakuraWorldConfigCreator.get(); // Sakura - sakura configuration files + this.generator = gen; + this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); + diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch new file mode 100644 index 0000000..db1ff38 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch @@ -0,0 +1,40 @@ +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -306,6 +_,7 @@ + return (float)missedRays / (float)totalRays; + } + // Paper end - collisions optimisations ++ private final boolean consistentRadius; // Sakura - consistent explosion radius + + public ServerExplosion( + ServerLevel level, +@@ -326,6 +_,7 @@ + this.damageSource = damageSource == null ? level.damageSources().explosion(this) : damageSource; + this.damageCalculator = damageCalculator == null ? this.makeDamageCalculator(source) : damageCalculator; + this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit ++ this.consistentRadius = level.localConfig().config(BlockPos.containing(this.center)).consistentRadius; // Sakura - consistent explosion radius + } + + private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { +@@ -415,7 +_,7 @@ + + ray += 3; + +- float power = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F); ++ float power = this.radius * (0.7F + (this.consistentRadius ? 0.7F : this.level.random.nextFloat()) * 0.6F); // Sakura - consistent explosion radius + + do { + final int blockX = Mth.floor(currX); +@@ -629,6 +_,12 @@ + .getBlockState(blockPos) + .onExplosionHit(this.level, blockPos, this, (itemStack, blockPos1) -> addOrAppendStack(list, itemStack, blockPos1)); + } ++ ++ // Sakura start - configure explosions dropping items ++ if (!this.level.sakuraConfig().cannons.explosion.explosionsDropItems) { ++ list.clear(); ++ } ++ // Sakura end - configure explosions dropping items + + for (ServerExplosion.StackCollector stackCollector : list) { + Block.popResource(this.level, stackCollector.pos, stackCollector.stack); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch new file mode 100644 index 0000000..a1a3bfe --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/block/BubbleColumnBlock.java ++++ b/net/minecraft/world/level/block/BubbleColumnBlock.java +@@ -49,6 +_,13 @@ + @Override + protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent ++ // Sakura start - configure bubble columns affecting cannon entities ++ if (!level.sakuraConfig().cannons.tntAndSandAffectedByBubbleColumns && ( ++ entity instanceof net.minecraft.world.entity.item.PrimedTnt || ++ entity instanceof net.minecraft.world.entity.item.FallingBlockEntity)) { ++ return; ++ } ++ // Sakura end - configure bubble columns affecting cannon entities + BlockState blockState = level.getBlockState(pos.above()); + if (blockState.isAir()) { + entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch new file mode 100644 index 0000000..07721c0 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/level/block/CactusBlock.java ++++ b/net/minecraft/world/level/block/CactusBlock.java +@@ -48,6 +_,19 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { ++ // Sakura start - use random chance for crop growth ++ if (level.sakuraConfig().environment.crops.useRandomChanceToGrow) { ++ final int modifier = level.spigotConfig.cactusModifier; ++ if (random.nextFloat() >= modifier / (100.0f * 16)) { ++ return; ++ } ++ // set crop age to max so it grows right away ++ state = state.setValue(CactusBlock.AGE, AGE.max); ++ } ++ this.ageAndGrow(state, level, pos, random); ++ } ++ private void ageAndGrow(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { ++ // Sakura end - use random chance for crop growth + BlockPos blockPos = pos.above(); + if (level.isEmptyBlock(blockPos)) { + int i = 1; +@@ -63,6 +_,11 @@ + if (ageValue >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, blockPos, this.defaultBlockState()); // CraftBukkit + BlockState blockState = state.setValue(AGE, Integer.valueOf(0)); ++ // Sakura start - use random chance for crop growth; fix cactus growing next to a block and not breaking ++ if (level.sakuraConfig().environment.crops.useRandomChanceToGrow) { ++ level.neighborShapeChanged(Direction.UP, blockPos, pos, state, 4, 1); ++ } ++ // Sakura end - use random chance for crop growth; fix cactus growing next to a block and not breaking + level.setBlock(pos, blockState, 4); + level.neighborChanged(blockState, blockPos, this, null, false); + } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/CarpetBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/CarpetBlock.java.patch new file mode 100644 index 0000000..6996036 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/CarpetBlock.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/level/block/CarpetBlock.java ++++ b/net/minecraft/world/level/block/CarpetBlock.java +@@ -15,6 +_,7 @@ + public class CarpetBlock extends Block { + public static final MapCodec CODEC = simpleCodec(CarpetBlock::new); + protected static final VoxelShape SHAPE = Block.box(0.0, 0.0, 0.0, 16.0, 1.0, 16.0); ++ private static final VoxelShape SHAPE_COPY = SHAPE.copy(); // Sakura - protect block shapes against plugins + + @Override + public MapCodec codec() { +@@ -27,7 +_,7 @@ + + @Override + protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { +- return SHAPE; ++ return SHAPE_COPY; // Sakura - protect block shapes against plugins + } + + @Override diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/IceBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/IceBlock.java.patch new file mode 100644 index 0000000..c2ae296 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/IceBlock.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/block/IceBlock.java ++++ b/net/minecraft/world/level/block/IceBlock.java +@@ -40,7 +_,7 @@ + public void afterDestroy(Level level, BlockPos pos, ItemStack stack) { + // Paper end - Improve Block#breakNaturally API + if (!EnchantmentHelper.hasTag(stack, EnchantmentTags.PREVENTS_ICE_MELTING)) { +- if (level.dimensionType().ultraWarm()) { ++ if (!level.sakuraConfig().environment.allowWaterInTheNether && level.dimensionType().ultraWarm()) { // Sakura - allow water in the nether + level.removeBlock(pos, false); + return; + } +@@ -65,7 +_,7 @@ + return; + } + // CraftBukkit end +- if (level.dimensionType().ultraWarm()) { ++ if (!level.sakuraConfig().environment.allowWaterInTheNether && level.dimensionType().ultraWarm()) { // Sakura - allow water in the nether + level.removeBlock(pos, false); + } else { + level.setBlockAndUpdate(pos, meltsInto()); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/LadderBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/LadderBlock.java.patch new file mode 100644 index 0000000..d941465 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/LadderBlock.java.patch @@ -0,0 +1,36 @@ +--- a/net/minecraft/world/level/block/LadderBlock.java ++++ b/net/minecraft/world/level/block/LadderBlock.java +@@ -29,6 +_,12 @@ + protected static final VoxelShape WEST_AABB = Block.box(13.0, 0.0, 0.0, 16.0, 16.0, 16.0); + protected static final VoxelShape SOUTH_AABB = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 3.0); + protected static final VoxelShape NORTH_AABB = Block.box(0.0, 0.0, 13.0, 16.0, 16.0, 16.0); ++ // Sakura start - protect block shapes against plugins ++ private static final VoxelShape EAST_AABB_COPY = EAST_AABB.copy(); ++ private static final VoxelShape WEST_AABB_COPY = WEST_AABB.copy(); ++ private static final VoxelShape SOUTH_AABB_COPY = SOUTH_AABB.copy(); ++ private static final VoxelShape NORTH_AABB_COPY = NORTH_AABB.copy(); ++ // Sakura end - protect block shapes against plugins + + @Override + public MapCodec codec() { +@@ -44,14 +_,16 @@ + protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + switch ((Direction)state.getValue(FACING)) { + case NORTH: +- return NORTH_AABB; ++ // Sakura start - protect block shapes against plugins ++ return NORTH_AABB_COPY; + case SOUTH: +- return SOUTH_AABB; ++ return SOUTH_AABB_COPY; + case WEST: +- return WEST_AABB; ++ return WEST_AABB_COPY; + case EAST: + default: +- return EAST_AABB; ++ return EAST_AABB_COPY; ++ // Sakura end - protect block shapes against plugins + } + } + diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch new file mode 100644 index 0000000..2966266 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/block/LiquidBlock.java ++++ b/net/minecraft/world/level/block/LiquidBlock.java +@@ -140,7 +_,7 @@ + } + // Paper start - Configurable speed for water flowing over lava + public int getFlowSpeed(Level level, BlockPos pos) { +- if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) { ++ 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 + if ( + isLava(level, pos.north(1)) || + isLava(level, pos.south(1)) || +@@ -150,7 +_,7 @@ + return level.paperConfig().environment.waterOverLavaFlowSpeed; + } + } +- return this.fluid.getTickDelay(level); ++ return this.fluid.getTickDelay(level, pos); // Sakura - lava flow speed api + } + private static boolean isLava(Level level, BlockPos pos) { + final FluidState fluidState = level.getFluidIfLoaded(pos); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/RedStoneWireBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/RedStoneWireBlock.java.patch new file mode 100644 index 0000000..0a595ae --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/RedStoneWireBlock.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -300,7 +_,7 @@ + * Note: Added 'source' argument so as to help determine direction of information flow + */ + private void updateSurroundingRedstone(Level worldIn, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean blockAdded) { +- if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { ++ if (worldIn.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { // Sakura - redstone implementation api + // since 24w33a the source pos is no longer given, but instead an Orientation parameter + // when this is not null, it can be used to find the source pos, which the turbo uses + // to find the direction of information flow +@@ -373,7 +_,7 @@ + protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) { + if (!oldState.is(state.getBlock()) && !level.isClientSide) { + // Paper start - optimize redstone - replace call to updatePowerStrength +- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ if (level.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api + level.getWireHandler().onWireAdded(pos, state); // Alternate Current + } else { + this.updateSurroundingRedstone(level, pos, state, null, true); // Vanilla/Eigencraft +@@ -398,7 +_,7 @@ + } + + // Paper start - optimize redstone - replace call to updatePowerStrength +- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ if (level.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api + level.getWireHandler().onWireRemoved(pos, state); // Alternate Current + } else { + this.updateSurroundingRedstone(level, pos, state, null, false); // Vanilla/Eigencraft +@@ -429,7 +_,7 @@ + if (!level.isClientSide) { + // Paper start - optimize redstone (Alternate Current) + // Alternate Current handles breaking of redstone wires in the WireHandler. +- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ if (level.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api + level.getWireHandler().onWireUpdated(pos, state, orientation); + } else + // Paper end - optimize redstone (Alternate Current) diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch new file mode 100644 index 0000000..6f4a80c --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/level/block/SugarCaneBlock.java ++++ b/net/minecraft/world/level/block/SugarCaneBlock.java +@@ -49,6 +_,19 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { ++ // Sakura start - use random chance for crop growth ++ if (level.sakuraConfig().environment.crops.useRandomChanceToGrow) { ++ final int modifier = level.spigotConfig.caneModifier; ++ if (random.nextFloat() >= modifier / (100.0f * 16)) { ++ return; ++ } ++ // set crop age to max so it grows right away ++ state = state.setValue(SugarCaneBlock.AGE, AGE.max); ++ } ++ this.ageAndGrow(state, level, pos, random); ++ } ++ private void ageAndGrow(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { ++ // Sakura end - use random chance for crop growth + if (level.isEmptyBlock(pos.above())) { + int i = 1; + +@@ -62,6 +_,7 @@ + if (ageValue >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos.above(), this.defaultBlockState()); // CraftBukkit + level.setBlock(pos, state.setValue(AGE, Integer.valueOf(0)), 4); ++ // Sakura - use random chance for crop growth; conflict on change + } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution + level.setBlock(pos, state.setValue(AGE, Integer.valueOf(ageValue + 1)), 4); + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch new file mode 100644 index 0000000..7617494 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +@@ -65,8 +_,11 @@ + int i = -1; + int i1 = 1; + ++ // Sakura start - configure random item dispensing ++ final boolean randomItemSelection = this.level.sakuraConfig().technical.dispenserRandomItemSelection || this instanceof DropperBlockEntity; + for (int i2 = 0; i2 < this.items.size(); i2++) { +- if (!this.items.get(i2).isEmpty() && random.nextInt(i1++) == 0) { ++ if (!this.items.get(i2).isEmpty() && (!randomItemSelection && i == -1 || randomItemSelection && random.nextInt(i1++) == 0)) { ++ // Sakura end - configure random item dispensing + i = i2; + } + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch new file mode 100644 index 0000000..7671b9f --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -392,6 +_,11 @@ + for (int i1 = toPush.size() - 1; i1 >= 0; i1--) { + // Paper start - fix a variety of piston desync dupes + boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; ++ // Sakura start - configure tnt duplication ++ if (level.sakuraConfig().technical.allowTNTDuplication && list.get(i1).is(Blocks.TNT)) { ++ allowDesync = true; ++ } ++ // Sakura end - configure tnt duplication + BlockPos blockPos2; + BlockPos oldPos = blockPos2 = toPush.get(i1); + BlockState blockState1 = allowDesync ? level.getBlockState(oldPos) : null; diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch new file mode 100644 index 0000000..8f5df7f --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -43,6 +_,11 @@ + private float progressO; + private long lastTicked; + private int deathTicks; ++ // Sakura start - cache moving block entity collision shape ++ private VoxelShape collisionShape = Shapes.empty(); ++ private Direction shapeDirection = null; ++ private float shapeProgress = Float.MIN_VALUE; ++ // Sakura end - cache moving block entity collision shape + + public PistonMovingBlockEntity(BlockPos pos, BlockState blockState) { + super(BlockEntityType.PISTON, pos, blockState); +@@ -351,6 +_,18 @@ + } + + public VoxelShape getCollisionShape(BlockGetter level, BlockPos pos) { ++ // Sakura start - cache moving block entity collision shape ++ Direction direction = NOCLIP.get(); ++ if (this.progress == this.shapeProgress && direction == this.shapeDirection) { ++ return this.collisionShape; ++ } else { ++ this.shapeProgress = this.progress; ++ this.shapeDirection = direction; ++ return this.collisionShape = this.createCollisionShape(level, pos); ++ } ++ } ++ private VoxelShape createCollisionShape(BlockGetter level, BlockPos pos) { ++ // Sakura end - cache moving block entity collision shape + VoxelShape collisionShape; + if (!this.extending && this.isSourcePiston && this.movedState.getBlock() instanceof PistonBaseBlock) { + collisionShape = this.movedState.setValue(PistonBaseBlock.EXTENDED, Boolean.valueOf(true)).getCollisionShape(level, pos); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch new file mode 100644 index 0000000..515439f --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -310,12 +_,18 @@ + + public void fillBiomesFromNoise(BiomeResolver biomeResolver, Climate.Sampler climateSampler, int x, int y, int z) { + PalettedContainer> palettedContainer = this.biomes.recreate(); ++ Holder biome = null; // Sakura - calculate biome noise for each chunk section + int i = 4; + + for (int i1 = 0; i1 < 4; i1++) { + for (int i2 = 0; i2 < 4; i2++) { + for (int i3 = 0; i3 < 4; i3++) { +- palettedContainer.getAndSetUnchecked(i1, i2, i3, biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler)); ++ // Sakura start - calculate biome noise once for each chunk section ++ if (biome == null || !me.samsuik.sakura.configuration.GlobalConfiguration.get().environment.calculateBiomeNoiseOncePerChunkSection) { ++ biome = biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler); ++ } ++ palettedContainer.getAndSetUnchecked(i1, i2, i3, biome); ++ // Sakura end - calculate biome noise once for each chunk section + } + } + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch new file mode 100644 index 0000000..4f9a9d5 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch @@ -0,0 +1,61 @@ +--- a/net/minecraft/world/level/material/FlowingFluid.java ++++ b/net/minecraft/world/level/material/FlowingFluid.java +@@ -157,7 +_,7 @@ + BlockState blockState1 = level.getBlockState(blockPos); + FluidState fluidState1 = blockState1.getFluidState(); + if (this.canMaybePassThrough(level, pos, blockState, Direction.DOWN, blockPos, blockState1, fluidState1)) { +- FluidState newLiquid = this.getNewLiquid(level, blockPos, blockState1); ++ FluidState newLiquid = this.getLiquid(level, blockPos, blockState1, pos, blockState); // Sakura - optimise new liquid level + Fluid type = newLiquid.getType(); + if (fluidState1.canBeReplacedWith(level, blockPos, type, Direction.DOWN) && canHoldSpecificFluid(level, blockPos, blockState1, type)) { + // CraftBukkit start +@@ -214,6 +_,23 @@ + } + + protected FluidState getNewLiquid(ServerLevel level, BlockPos pos, BlockState state) { ++ // Sakura start - optimise new liquid level ++ final BlockPos abovePos = pos.above(); ++ final BlockState aboveState = level.getBlockState(abovePos); ++ return this.getLiquid(level, pos, state, abovePos, aboveState); ++ } ++ ++ private FluidState getLiquid(final ServerLevel world, final BlockPos flowToPos, final BlockState flowToState, final BlockPos abovePos, final BlockState aboveState) { ++ final FluidState aboveFluid = aboveState.getFluidState(); ++ if (!aboveFluid.isEmpty() && aboveFluid.getType().isSame(this) && FlowingFluid.canPassThroughWall(Direction.UP, world, flowToPos, flowToState, abovePos, aboveState)) { ++ return this.getFlowing(8, true); ++ } else { ++ return this.getLiquidFromSurroundings(world, flowToPos, flowToState); ++ } ++ } ++ ++ private FluidState getLiquidFromSurroundings(final ServerLevel level, final BlockPos pos, final BlockState state) { ++ // Sakura start - optimise new liquid level + int i = 0; + int i1 = 0; + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); +@@ -240,12 +_,7 @@ + } + } + +- BlockPos blockPos1 = mutableBlockPos.setWithOffset(pos, Direction.UP); +- BlockState blockState2 = level.getBlockState(blockPos1); +- FluidState fluidState2 = blockState2.getFluidState(); +- if (!fluidState2.isEmpty() && fluidState2.getType().isSame(this) && canPassThroughWall(Direction.UP, level, pos, state, blockPos1, blockState2)) { +- return this.getFlowing(8, true); +- } else { ++ { // Sakura - optimise new liquid level + int i2 = i - this.getDropOff(level); + return i2 <= 0 ? Fluids.EMPTY.defaultFluidState() : this.getFlowing(i2, false); + } +@@ -464,6 +_,11 @@ + } + + private static boolean canHoldSpecificFluid(BlockGetter level, BlockPos pos, BlockState state, Fluid fluid) { ++ // Sakura start - configure fluids breaking redstone components ++ if (level instanceof Level gameLevel && !gameLevel.sakuraConfig().technical.redstone.fluidsBreakRedstone && (state.isSignalSource() || state.getBlock() instanceof net.minecraft.world.level.block.CarpetBlock)) { ++ return false; ++ } ++ // Sakura end - configure fluids breaking redstone components + return !(state.getBlock() instanceof LiquidBlockContainer liquidBlockContainer) || liquidBlockContainer.canPlaceLiquid(null, level, pos, state, fluid); + } + diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/Fluid.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/Fluid.java.patch new file mode 100644 index 0000000..cc9bae7 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/Fluid.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/level/material/Fluid.java ++++ b/net/minecraft/world/level/material/Fluid.java +@@ -67,6 +_,12 @@ + + protected abstract boolean canBeReplacedWith(FluidState state, BlockGetter level, BlockPos pos, Fluid fluid, Direction direction); + ++ // Sakura start - lava flow speed api ++ public int getTickDelay(final Level world, final BlockPos pos) { ++ return this.getTickDelay(world); ++ } ++ // Sakura end - lava flow speed api ++ + protected abstract Vec3 getFlow(BlockGetter blockReader, BlockPos pos, FluidState fluidState); + + public abstract int getTickDelay(LevelReader level); diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch new file mode 100644 index 0000000..7b6837c --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/level/material/LavaFluid.java ++++ b/net/minecraft/world/level/material/LavaFluid.java +@@ -177,12 +_,20 @@ + + @Override + public int getTickDelay(LevelReader level) { +- return level.dimensionType().ultraWarm() ? 10 : 30; +- } ++ return level.dimensionType().ultraWarm() && !(level instanceof Level gameLevel && gameLevel.sakuraConfig().environment.disableFastNetherLava) ? 10 : 30; // Sakura - configure fast nether lava ++ } ++ ++ // Sakura start - lava flow speed api ++ @Override ++ public final int getTickDelay(Level world, BlockPos pos) { ++ final int flowSpeed = world.localConfig().config(pos).lavaFlowSpeed; ++ return flowSpeed >= 0 ? flowSpeed : this.getTickDelay(world); ++ } ++ // Sakura end - lava flow speed api + + @Override + public int getSpreadDelay(Level level, BlockPos pos, FluidState currentState, FluidState newState) { +- int tickDelay = this.getTickDelay(level); ++ int tickDelay = this.getTickDelay(level, pos); // Sakura - lava flow speed api + if (!currentState.isEmpty() + && !newState.isEmpty() + && !currentState.getValue(FALLING) diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java.patch new file mode 100644 index 0000000..12bedbb --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java ++++ b/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java +@@ -18,6 +_,7 @@ + orientation = orientation.withFront(front); + } + // Paper start - Optimize redstone (Alternate Current) - use default front instead of random ++ // Sakura - redstone implementation api; conflict on change + else if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { + orientation = orientation.withFront(Direction.WEST); + } diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/phys/shapes/VoxelShape.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/phys/shapes/VoxelShape.java.patch new file mode 100644 index 0000000..c4b3107 --- /dev/null +++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/phys/shapes/VoxelShape.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -433,6 +_,12 @@ + ); + } + // Paper end - optimise collisions ++ // Sakura start - protect block shapes against plugins ++ public final VoxelShape copy() { ++ this.cachedToAABBs = null; ++ return this.move(Vec3.ZERO); ++ } ++ // Sakura end - protect block shapes against plugins + + protected VoxelShape(DiscreteVoxelShape shape) { + this.shape = shape; diff --git a/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java.patch b/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java.patch new file mode 100644 index 0000000..c404dba --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java ++++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +@@ -61,7 +_,7 @@ + + @Override + public boolean isBrandCompatible(final @NotNull Key brandId) { +- return brandId.equals(this.brandId); ++ return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID); // Sakura - branding changes + } + + @Override diff --git a/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/Configurations.java.patch b/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/Configurations.java.patch new file mode 100644 index 0000000..dc36db5 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/Configurations.java.patch @@ -0,0 +1,20 @@ +--- a/src/main/java/io/papermc/paper/configuration/Configurations.java ++++ b/src/main/java/io/papermc/paper/configuration/Configurations.java +@@ -94,7 +_,7 @@ + }; + } + +- static CheckedFunction reloader(Class type, T instance) { ++ public static CheckedFunction reloader(Class type, T instance) { // Sakura - package-protected -> public + return node -> { + ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type)); + ObjectMapper.Mutable mutable = (ObjectMapper.Mutable) factory.get(type); +@@ -229,7 +_,7 @@ + .path(worldConfigFile) + .build(); + final ConfigurationNode worldNode = worldLoader.load(); +- if (newFile) { // set the version field if new file ++ if (newFile && this instanceof PaperConfigurations) { // Sakura - hack this into working // set the version field if new file + worldNode.node(Configuration.VERSION_FIELD).set(this.worldConfigVersion()); + } else { + this.verifyWorldConfigVersion(contextMap, worldNode); diff --git a/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java.patch b/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java.patch new file mode 100644 index 0000000..56aab1f --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java.patch @@ -0,0 +1,20 @@ +--- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java ++++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +@@ -169,7 +_,7 @@ + .defaultOptions(PaperConfigurations::defaultOptions); + } + +- private static ConfigurationOptions defaultOptions(ConfigurationOptions options) { ++ public static ConfigurationOptions defaultOptions(ConfigurationOptions options) { // Sakura - sakura configuration files + return options.serializers(builder -> builder + .register(MapSerializer.TYPE, new MapSerializer(false)) + .register(new EnumValueSerializer()) +@@ -466,7 +_,7 @@ + } + + // Symlinks are not correctly checked in createDirectories +- static void createDirectoriesSymlinkAware(Path path) throws IOException { ++ public static void createDirectoriesSymlinkAware(Path path) throws IOException { // Sakura - package-protected -> public + if (!Files.isDirectory(path)) { + Files.createDirectories(path); + } diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch new file mode 100644 index 0000000..3c56cf8 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch @@ -0,0 +1,18 @@ +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1087,6 +_,7 @@ + + org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot + this.console.paperConfigurations.reloadConfigs(this.console); ++ this.console.sakuraConfigurations.reloadConfigs(this.console); // Sakura - sakura configuration files; missing comment above :< + for (ServerLevel world : this.console.getAllLevels()) { + // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty + world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) +@@ -1118,6 +_,7 @@ + this.reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot + io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper ++ me.samsuik.sakura.command.SakuraCommands.registerCommands(this.console); // Sakura - sakura configuration files + this.spark.registerCommandBeforePlugins(this); // Paper - spark + this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); + this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch new file mode 100644 index 0000000..dd86e06 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch @@ -0,0 +1,15 @@ +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -285,6 +_,12 @@ + ).isValid(); + } + // Paper end ++ // Sakura start - local config and property storage ++ @Override ++ public final me.samsuik.sakura.local.storage.LocalStorageHandler getStorageHandler() { ++ return this.getHandle().localConfig(); ++ } ++ // Sakura end - local config and property storage + + private static final Random rand = new Random(); + diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/Main.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/Main.java.patch new file mode 100644 index 0000000..628cad3 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/Main.java.patch @@ -0,0 +1,17 @@ +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -176,6 +_,14 @@ + .describedAs("Jar file"); + // Paper end + ++ // Sakura start - sakura configuration files ++ acceptsAll(asList("sakura-dir", "sakura-settings-directory"), "Directory for Sakura settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File(me.samsuik.sakura.configuration.SakuraConfigurations.CONFIG_DIR)) ++ .describedAs("Config directory"); ++ // Sakura end - sakura configuration files ++ + // Paper start + acceptsAll(asList("server-name"), "Name of the server") + .withRequiredArg() diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch new file mode 100644 index 0000000..52fab98 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch @@ -0,0 +1,21 @@ +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -115,6 +_,18 @@ + throw new AssertionError("Unknown entity " + (entity == null ? null : entity.getClass())); + } + ++ // Sakura start - entity pushed by fluid api ++ @Override ++ public final boolean isPushedByFluid() { ++ return this.getHandle().isPushedByFluid(); ++ } ++ ++ @Override ++ public final void setPushedByFluid(boolean push) { ++ this.getHandle().pushedByFluid = push; ++ } ++ // Sakura end - entity pushed by fluid api ++ + @Override + public Location getLocation() { + return CraftLocation.toBukkit(this.entity.position(), this.getWorld(), this.entity.getBukkitYaw(), this.entity.getXRot()); diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch new file mode 100644 index 0000000..a8bdc7c --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch @@ -0,0 +1,21 @@ +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +@@ -14,6 +_,18 @@ + super(server, entity); + } + ++ // Sakura start - falling block height parity api ++ @Override ++ public final void setHeightParity(boolean parity) { ++ this.getHandle().heightParity = parity; ++ } ++ ++ @Override ++ public final boolean getHeightParity() { ++ return this.getHandle().heightParity; ++ } ++ // Sakura end - falling block height parity api ++ + @Override + public FallingBlockEntity getHandle() { + return (FallingBlockEntity) this.entity; diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch new file mode 100644 index 0000000..a5d8db3 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch @@ -0,0 +1,21 @@ +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3003,6 +_,18 @@ + return (this.getHandle().requestedViewDistance() == 0) ? Bukkit.getViewDistance() : this.getHandle().requestedViewDistance(); + } + ++ // Sakura start - entity tracking range modifier ++ @Override ++ public double getTrackingRangeModifier() { ++ return this.getHandle().trackingRangeModifier * 100.0; ++ } ++ ++ @Override ++ public void setTrackingRangeModifier(double mod) { ++ this.getHandle().trackingRangeModifier = mod / 100.0; ++ } ++ // Sakura end - entity tracking range modifier ++ + // Paper start + @Override + public java.util.Locale locale() { diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.patch new file mode 100644 index 0000000..2f0a0b4 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +@@ -11,7 +_,7 @@ + public static String getBukkitVersion() { + String result = "Unknown-Version"; + +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/io.papermc.paper/paper-api/pom.properties"); ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/me.samsuik.sakura/sakura-api/pom.properties"); // Sakura - branding changes + Properties properties = new Properties(); + + if (stream != null) { diff --git a/sakura-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch b/sakura-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch new file mode 100644 index 0000000..ab76505 --- /dev/null +++ b/sakura-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -228,7 +_,7 @@ + } + + private static void tpsCommand() { +- SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps")); ++ // Sakura - track tick information; replace tps command + } + + public static int playerSample; diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java new file mode 100644 index 0000000..07e8007 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java @@ -0,0 +1,72 @@ +package me.samsuik.sakura.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +@DefaultQualifier(NonNull.class) +public abstract class BaseSubCommand extends Command { + public BaseSubCommand(String name) { + super(name); + this.description = "Sakura Command " + name; + this.setPermission("bukkit.command." + name); + } + + public abstract void execute(CommandSender sender, String[] args); + + public void tabComplete(List list, String[] args) throws IllegalArgumentException {} + + @Override + @Deprecated + public final boolean execute(CommandSender sender, String label, String[] args) { + if (this.testPermission(sender)) { + this.execute(sender, args); + } + + return true; + } + + @Override + @NotNull + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + List completions = new ArrayList<>(0); + + if (this.testPermissionSilent(sender)) { + this.tabComplete(completions, args); + } + + return completions; + } + + protected final Optional parseInt(String[] args, int index) { + return this.parse(args, index, Integer::parseInt); + } + + protected final Optional parseLong(String[] args, int index) { + return this.parse(args, index, Long::parseLong); + } + + protected final Optional parseFloat(String[] args, int index) { + return this.parse(args, index, Float::parseFloat); + } + + protected final Optional parseDouble(String[] args, int index) { + return this.parse(args, index, Double::parseDouble); + } + + protected final Optional parse(String[] args, int index, Function func) { + try { + String arg = args[index]; + return Optional.of(func.apply(arg)); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) { + return Optional.empty(); + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java new file mode 100644 index 0000000..f46883c --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java @@ -0,0 +1,86 @@ +package me.samsuik.sakura.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +@DefaultQualifier(NonNull.class) +public final class SakuraCommand extends Command { + private static final Component HEADER_MESSAGE = MiniMessage.miniMessage().deserialize(""" + . + | This is the main command for Sakura. + | All exclusive commands are listed below.""" + ); + + private static final String COMMAND_MSG = "| * /"; + + public SakuraCommand(String name) { + super(name); + this.description = ""; + this.usageMessage = "/sakura"; + this.setPermission("bukkit.command.sakura"); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (args.length > 0) { + List commands = new ArrayList<>(SakuraCommands.COMMANDS.values()); + + // This part is copied from the VersionCommand SubCommand in paper + Command internalVersion = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); + if (internalVersion != null) { + commands.add(internalVersion); + } + + for (Command base : commands) { + if (base.getName().equalsIgnoreCase(args[0])) { + return base.execute(sender, commandLabel, Arrays.copyOfRange(args, 1, args.length)); + } + } + } + + this.sendHelpMessage(sender); + return false; + } + + private void sendHelpMessage(CommandSender sender) { + sender.sendMessage(HEADER_MESSAGE); + + Stream uniqueCommands = SakuraCommands.COMMANDS.values() + .stream() + .filter(command -> command != this); + + uniqueCommands.forEach((command) -> { + sender.sendRichMessage(COMMAND_MSG, Placeholder.unparsed("command", command.getName())); + }); + + sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE)); + } + + @NotNull + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + if (!this.testPermissionSilent(sender)) { + return Collections.emptyList(); + } + + return SakuraCommands.COMMANDS.values().stream() + .filter(command -> command != this) + .map(Command::getName) + .filter(name -> args.length <= 1 || name.startsWith(args[args.length - 1])) + .toList(); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java new file mode 100644 index 0000000..30393fd --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java @@ -0,0 +1,24 @@ +package me.samsuik.sakura.command; + +import me.samsuik.sakura.command.subcommands.ConfigCommand; +import me.samsuik.sakura.command.subcommands.TPSCommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; + +import java.util.HashMap; +import java.util.Map; + +public final class SakuraCommands { + static final Map COMMANDS = new HashMap<>(); + static { + COMMANDS.put("sakura", new SakuraCommand("sakura")); + COMMANDS.put("config", new ConfigCommand("config")); + COMMANDS.put("tps", new TPSCommand("tps")); + } + + public static void registerCommands(MinecraftServer server) { + COMMANDS.forEach((s, command) -> { + server.server.getCommandMap().register(s, "sakura", command); + }); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java new file mode 100644 index 0000000..c41f188 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java @@ -0,0 +1,33 @@ +package me.samsuik.sakura.command.subcommands; + +import me.samsuik.sakura.command.BaseSubCommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@DefaultQualifier(NonNull.class) +public final class ConfigCommand extends BaseSubCommand { + public ConfigCommand(String name) { + super(name); + this.description = "Command for reloading the sakura configuration file"; + } + + @Override + public void execute(CommandSender sender, String[] args) { + Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); + Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); + + MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); + server.sakuraConfigurations.reloadConfigs(server); + server.server.reloadCount++; + + Command.broadcastCommandMessage(sender, text("Sakura config reload complete.", GREEN)); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java new file mode 100644 index 0000000..db6d03c --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/TPSCommand.java @@ -0,0 +1,92 @@ +package me.samsuik.sakura.command.subcommands; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import me.samsuik.sakura.command.BaseSubCommand; +import me.samsuik.sakura.tps.ServerTickInformation; +import me.samsuik.sakura.tps.graph.BuiltComponentCanvas; +import me.samsuik.sakura.tps.graph.DetailedTPSGraph; +import me.samsuik.sakura.tps.graph.GraphComponents; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.CommandSender; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class TPSCommand extends BaseSubCommand { + private static final int GRAPH_WIDTH = 71; + private static final int GRAPH_HEIGHT = 10; + private static final Style GRAY_WITH_STRIKETHROUGH = Style.style(NamedTextColor.GRAY, TextDecoration.STRIKETHROUGH); + + public TPSCommand(String name) { + super(name); + this.description = "Displays the current ticks per second"; + } + + @Override + public void execute(CommandSender sender, String[] args) { + ServerTickInformation tickInformation = MinecraftServer.getServer().latestTickInformation(); + long identifier = this.parseLong(args, 1).orElse(tickInformation.identifier()); + double scale = this.parseDouble(args, 0).orElse(-1.0); + if (scale < 0.0) { + scale = this.dynamicScale(identifier); + } + + ImmutableList tickHistory = MinecraftServer.getServer().tickHistory(identifier - GRAPH_WIDTH, identifier); + DetailedTPSGraph graph = new DetailedTPSGraph(GRAPH_WIDTH, GRAPH_HEIGHT, scale, tickHistory); + BuiltComponentCanvas canvas = graph.plot(); + canvas.appendLeft(Component.text(":", NamedTextColor.BLACK)); + canvas.appendRight(Component.text(":", NamedTextColor.BLACK)); + canvas.header(this.createHeaderComponent(tickInformation, identifier)); + canvas.footer(Component.text("*", NamedTextColor.DARK_GRAY) + .append(Component.text(Strings.repeat(" ", GRAPH_WIDTH - 1), GRAY_WITH_STRIKETHROUGH)) + .append(Component.text("*"))); + + for (Component component : canvas.components()) { + sender.sendMessage(component); + } + } + + private double dynamicScale(long identifier) { + ImmutableList tickHistory = MinecraftServer.getServer().tickHistory(identifier - 5, identifier); + double averageTps = tickHistory.stream() + .mapToDouble(ServerTickInformation::tps) + .average() + .orElse(0.0); + return 20 / averageTps; + } + + private Component createHeaderComponent(ServerTickInformation tickInformation, long identifier) { + int scrollAmount = GRAPH_WIDTH / 3 * 2; + double memoryUsage = memoryUsage(); + TextComponent.Builder builder = Component.text(); + builder.color(NamedTextColor.DARK_GRAY); + builder.append(Component.text("< ") + .clickEvent(ClickEvent.runCommand("/tps -1 " + (identifier + scrollAmount)))); + builder.append(Component.text(Strings.repeat(" ", 19), GRAY_WITH_STRIKETHROUGH)); + builder.append(Component.text(" ( ")); + builder.append(Component.text("Now: ", NamedTextColor.WHITE) + .append(Component.text("%.1f".formatted(tickInformation.tps()), tickInformation.colour()))); + builder.appendSpace(); + builder.append(Component.text("Mem: ", NamedTextColor.WHITE) + .append(Component.text("%.1f".formatted(memoryUsage * 100), GraphComponents.colour(1 - (float) memoryUsage)))); + builder.append(Component.text("% ) ")); + builder.append(Component.text(Strings.repeat(" ", 18), GRAY_WITH_STRIKETHROUGH)); + builder.append(Component.text(" >") + .clickEvent(ClickEvent.runCommand("/tps -1 " + (identifier - scrollAmount)))); + return builder.build(); + } + + private static double memoryUsage() { + Runtime runtime = Runtime.getRuntime(); + double free = runtime.freeMemory(); + double max = runtime.maxMemory(); + double alloc = runtime.totalMemory(); + return (alloc - free) / max; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java new file mode 100644 index 0000000..4f7208b --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java @@ -0,0 +1,63 @@ +package me.samsuik.sakura.configuration; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.Configuration; +import io.papermc.paper.configuration.ConfigurationPart; +import io.papermc.paper.configuration.type.number.IntOr; +import org.bukkit.Material; +import org.slf4j.Logger; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Setting; + +@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic", "RedundantSuppression"}) +public final class GlobalConfiguration extends ConfigurationPart { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + static final int CURRENT_VERSION = 3;// (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs + + private static GlobalConfiguration instance; + public static GlobalConfiguration get() { + return instance; + } + + static void set(GlobalConfiguration instance) { + GlobalConfiguration.instance = instance; + } + + @Setting(Configuration.VERSION_FIELD) + public int version = CURRENT_VERSION; + + public Messages messages; + public class Messages extends ConfigurationPart { + public String durableBlockInteraction = "(S) This block has of "; + public String fpsSettingChange = "(S) "; + public boolean tpsShowEntityAndChunkCount = true; + } + + public Fps fps; + public class Fps extends ConfigurationPart { + public Material material = Material.PINK_STAINED_GLASS_PANE; + } + + public Players players; + public class Players extends ConfigurationPart { + public IntOr.Default bucketStackSize = IntOr.Default.USE_DEFAULT; + } + + public Environment environment; + public class Environment extends ConfigurationPart { + @Comment("This is only intended for plot worlds. Will affect chunk generation on servers.") + public boolean calculateBiomeNoiseOncePerChunkSection = false; + + public MobSpawnerDefaults mobSpawnerDefaults = new MobSpawnerDefaults(); + public class MobSpawnerDefaults extends ConfigurationPart { + public int minSpawnDelay = 200; + public int maxSpawnDelay = 800; + public int spawnCount = 4; + public int maxNearbyEntities = 6; + public int requiredPlayerRange = 16; + public int spawnRange = 4; + } + } + +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java new file mode 100644 index 0000000..4462051 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java @@ -0,0 +1,228 @@ +package me.samsuik.sakura.configuration; + +import com.google.common.collect.Table; +import com.mojang.logging.LogUtils; +import io.leangen.geantyref.TypeToken; +import io.papermc.paper.configuration.*; +import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer; +import io.papermc.paper.configuration.serializer.*; +import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer; +import io.papermc.paper.configuration.serializer.collections.TableSerializer; +import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer; +import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2LongMap; +import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; +import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; +import org.slf4j.Logger; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.objectmapping.FieldDiscoverer; +import org.spongepowered.configurate.objectmapping.ObjectMapper; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Function; + +import static io.leangen.geantyref.GenericTypeReflector.erase; + +@NullMarked +@SuppressWarnings("Convert2Diamond") +public final class SakuraConfigurations extends Configurations { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + static final String GLOBAL_CONFIG_FILE_NAME = "sakura-global.yml"; + static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "sakura-world-defaults.yml"; + static final String WORLD_CONFIG_FILE_NAME = "sakura-world.yml"; + public static final String CONFIG_DIR = "config"; + + private static final String GLOBAL_HEADER = String.format(""" + This is the global configuration file for Sakura. + As you can see, there's a lot to configure. Some options may impact gameplay, so use + with caution, and make sure you know what each option does before configuring. + + The world configuration options have been moved inside + their respective world folder. The files are named %s""", WORLD_CONFIG_FILE_NAME); + + private static final String WORLD_DEFAULTS_HEADER = """ + This is the world defaults configuration file for Sakura. + As you can see, there's a lot to configure. Some options may impact gameplay, so use + with caution, and make sure you know what each option does before configuring. + + Configuration options here apply to all worlds, unless you specify overrides inside + the world-specific config file inside each world folder."""; + + private static final Function WORLD_HEADER = map -> String.format(""" + This is a world configuration file for Sakura. + This file may start empty but can be filled with settings to override ones in the %s/%s + + World: %s (%s)""", + SakuraConfigurations.CONFIG_DIR, + SakuraConfigurations.WORLD_DEFAULTS_CONFIG_FILE_NAME, + map.require(WORLD_NAME), + map.require(WORLD_KEY) + ); + + public SakuraConfigurations(final Path globalFolder) { + super(globalFolder, GlobalConfiguration.class, WorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME); + } + + @Override + protected YamlConfigurationLoader.Builder createLoaderBuilder() { + return super.createLoaderBuilder() + .defaultOptions(PaperConfigurations::defaultOptions); + } + + @Override + protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() { + return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder()); + } + + private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) { + return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig()); + } + + @Override + protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) { + return super.createGlobalLoaderBuilder(registryAccess) + .defaultOptions(SakuraConfigurations::defaultGlobalOptions); + } + + private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) { + return options + .header(GLOBAL_HEADER) + .serializers(builder -> builder + .register(new PacketClassSerializer()) + ); + } + + @Override + public GlobalConfiguration initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException { + GlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess); + GlobalConfiguration.set(configuration); + return configuration; + } + + @Override + protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) { + return super.createWorldObjectMapperFactoryBuilder(contextMap) + .addNodeResolver(new NestedSetting.Factory()) + .addDiscoverer(createWorldConfigFieldDiscoverer(contextMap)); + } + + private static FieldDiscoverer createWorldConfigFieldDiscoverer(final ContextMap contextMap) { + final Map, Object> overrides = Map.of( + WorldConfiguration.class, createWorldConfigInstance(contextMap) + ); + return new InnerClassFieldDiscoverer(overrides); + } + + private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) { + return new WorldConfiguration(contextMap.require(Configurations.WORLD_KEY)); + } + + @Override + protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) { + final RegistryAccess access = contextMap.require(REGISTRY_ACCESS); + return super.createWorldConfigLoaderBuilder(contextMap) + .defaultOptions(options -> options + .header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap)) + .serializers(serializers -> serializers + .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2IntOpenHashMap::new, Integer.TYPE)) + .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2LongOpenHashMap::new, Long.TYPE)) + .register(new TypeToken>() {}, new TableSerializer()) + .register(StringRepresentableSerializer::isValidFor, new StringRepresentableSerializer()) + .register(new RegistryValueSerializer<>(new TypeToken>() {}, access, Registries.ENTITY_TYPE, true)) + .register(new RegistryValueSerializer<>(Item.class, access, Registries.ITEM, true)) + .register(new RegistryValueSerializer<>(Block.class, access, Registries.BLOCK, true)) + .register(new RegistryHolderSerializer<>(new TypeToken>() {}, access, Registries.CONFIGURED_FEATURE, false)) + ) + ); + } + + @Override + protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node, final @Nullable ConfigurationNode defaultsNode) throws ConfigurateException { + ConfigurationTransformations.worldTransformations(node); + } + + @Override + protected void applyGlobalConfigTransformations(final ConfigurationNode node) throws ConfigurateException { + ConfigurationTransformations.globalTransformations(node); + } + + @Override + public WorldConfiguration createWorldConfig(final ContextMap contextMap) { + final String levelName = contextMap.require(WORLD_NAME); + try { + return super.createWorldConfig(contextMap); + } catch (IOException exception) { + throw new RuntimeException("Could not create world config for " + levelName, exception); + } + } + + @Override + protected boolean isConfigType(final Type type) { + return ConfigurationPart.class.isAssignableFrom(erase(type)); + } + + @Override + protected int globalConfigVersion() { + return GlobalConfiguration.CURRENT_VERSION; + } + + @Override + protected int worldConfigVersion() { + return WorldConfiguration.CURRENT_VERSION; + } + + public void reloadConfigs(MinecraftServer server) { + try { + this.initializeGlobalConfiguration(server.registryAccess(), reloader(this.globalConfigClass, GlobalConfiguration.get())); + this.initializeWorldDefaultsConfiguration(server.registryAccess()); + for (ServerLevel level : server.getAllLevels()) { + this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.sakuraConfig())); + } + } catch (Exception ex) { + throw new RuntimeException("Could not reload sakura configuration files", ex); + } + } + + private static ContextMap createWorldContextMap(ServerLevel level) { + return createWorldContextMap(level.levelStorageAccess.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.registryAccess()); + } + + public static ContextMap createWorldContextMap(Path dir, String levelName, ResourceLocation worldKey, RegistryAccess registryAccess) { + return ContextMap.builder() + .put(WORLD_DIRECTORY, dir) + .put(WORLD_NAME, levelName) + .put(WORLD_KEY, worldKey) + .put(REGISTRY_ACCESS, registryAccess) + .build(); + } + + public static SakuraConfigurations setup(final Path configDir) { + try { + PaperConfigurations.createDirectoriesSymlinkAware(configDir); + return new SakuraConfigurations(configDir); + } catch (final IOException ex) { + throw new RuntimeException("Could not setup PaperConfigurations", ex); + } + } + +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java new file mode 100644 index 0000000..7631050 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java @@ -0,0 +1,242 @@ +package me.samsuik.sakura.configuration; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.Configuration; +import io.papermc.paper.configuration.ConfigurationPart; +import io.papermc.paper.configuration.NestedSetting; +import io.papermc.paper.configuration.PaperConfigurations; +import io.papermc.paper.configuration.type.number.DoubleOr; +import io.papermc.paper.configuration.type.number.IntOr; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import me.samsuik.sakura.entity.merge.MergeLevel; +import me.samsuik.sakura.explosion.durable.DurableMaterial; +import me.samsuik.sakura.physics.PhysicsVersion; +import net.minecraft.Util; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import org.slf4j.Logger; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Setting; + +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic", "RedundantSuppression"}) +public final class WorldConfiguration extends ConfigurationPart { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + static final int CURRENT_VERSION = 6; // (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs + + private transient final ResourceLocation worldKey; + WorldConfiguration(ResourceLocation worldKey) { + this.worldKey = worldKey; + } + + public boolean isDefault() { + return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY); + } + + @Setting(Configuration.VERSION_FIELD) + public int version = CURRENT_VERSION; + + public Cannons cannons; + public class Cannons extends ConfigurationPart { + public MergeLevel mergeLevel = MergeLevel.STRICT; + public boolean tntAndSandAffectedByBubbleColumns = true; + + @NestedSetting({"treat-collidable-blocks-as-full", "while-moving"}) + public boolean treatAllBlocksAsFullWhenMoving = false; + @NestedSetting({"treat-collidable-blocks-as-full", "moving-faster-than"}) + public double treatAllBlocksAsFullWhenMovingFasterThan = 64.0; + public boolean loadChunks = false; + + public Restrictions restrictions = new Restrictions(); + public class Restrictions extends ConfigurationPart { + @Comment("The amount of blocks that can be travelled before changing direction is restricted") + public IntOr.Disabled leftShootingThreshold = IntOr.Disabled.DISABLED; + @Comment( + "Maximum amount of blocks that a cannon can adjust\n" + + "It is recommended that this value kept sane and is more than 64 blocks" + ) + public IntOr.Disabled maxAdjustDistance = IntOr.Disabled.DISABLED; + } + + public Tnt tnt = new Tnt(); + public class Tnt extends ConfigurationPart { + public boolean forcePositionUpdates; + } + + public Sand sand = new Sand(); + public class Sand extends ConfigurationPart { + public boolean despawnInsideMovingPistons = true; + public boolean concreteSolidifyInWater = true; + + @NestedSetting({"prevent-stacking", "against-border"}) + public boolean preventAgainstBorder = false; + @NestedSetting({"prevent-stacking", "world-height"}) + public boolean preventAtWorldHeight = false; + + public boolean isFallingBlockInBounds(FallingBlockEntity entity) { + return (!this.preventAgainstBorder || !ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(entity.level().getWorldBorder(), entity.getBoundingBox().inflate(0.01))) + && (!this.preventAtWorldHeight || entity.blockPosition().getY() < entity.level().getMaxY() - 1); + } + } + + public Explosion explosion = new Explosion(); + public class Explosion extends ConfigurationPart { + public boolean optimiseProtectedRegions = true; + public boolean avoidRedundantBlockSearches = false; + public Map durableMaterials = Util.make(new Reference2ObjectOpenHashMap<>(), map -> { + map.put(Blocks.OBSIDIAN, new DurableMaterial(4, Blocks.COBBLESTONE.getExplosionResistance())); + map.put(Blocks.ANVIL, new DurableMaterial(3, Blocks.END_STONE.getExplosionResistance())); + map.put(Blocks.CHIPPED_ANVIL, new DurableMaterial(3, Blocks.END_STONE.getExplosionResistance())); + map.put(Blocks.DAMAGED_ANVIL, new DurableMaterial(3, Blocks.END_STONE.getExplosionResistance())); + }); + public boolean protectScaffoldingFromCreepers = false; + public boolean allowNonTntBreakingDurableBlocks = false; + public boolean destroyWaterloggedBlocks = false; + public boolean explodeLava = false; + public boolean consistentRadius = false; + public boolean explosionsHurtPlayers = true; + public boolean explosionsDropItems = true; + public boolean useBlockCacheAcrossExplosions = false; + } + + public Mechanics mechanics = new Mechanics(); + public class Mechanics extends ConfigurationPart { + public TNTSpread tntSpread = TNTSpread.ALL; + public boolean tntFlowsInWater = true; + public boolean fallingBlockParity = false; + public PhysicsVersion physicsVersion = PhysicsVersion.LATEST; + + public enum TNTSpread { + ALL, Y, NONE; + } + } + } + + public Technical technical; + public class Technical extends ConfigurationPart { + public boolean dispenserRandomItemSelection = true; + @Comment( + "Only tick hoppers when items are able to be moved\n" + + "This can cause issues with redstone contraptions that rely on DUD's to detect when hoppers fail to move items." + ) + public boolean optimiseIdleHopperTicking = true; + + public Redstone redstone = new Redstone(); + public class Redstone extends ConfigurationPart { + public boolean redstoneCache = false; + public boolean fluidsBreakRedstone = true; + } + + @Comment( + "Allow TNT duplication while `allow-piston-duplication` is disabled.\n" + + "This exists so servers can enable TNT duplication without reintroducing the other forms of piston duplication." + ) + public boolean allowTNTDuplication = false; + } + + public Players players; + public class Players extends ConfigurationPart { + public Combat combat = new Combat(); + public class Combat extends ConfigurationPart { + public boolean legacyCombatMechanics = false; + public boolean allowSweepAttacks = true; + public boolean shieldDamageReduction = false; + public boolean oldEnchantedGoldenApple = false; + public boolean oldSoundsAndParticleEffects = false; + public boolean fastHealthRegen = true; + public IntOr.Default maxArmourDamage = IntOr.Default.USE_DEFAULT; + } + + public Knockback knockback = new Knockback(); + public class Knockback extends ConfigurationPart { + public DoubleOr.Default knockbackVertical = DoubleOr.Default.USE_DEFAULT; + public double knockbackVerticalLimit = 0.4; + public boolean verticalKnockbackRequireGround = true; + public double baseKnockback = 0.4; + @Comment("Knockback caused by sweeping edge") + public double sweepingEdgeKnockback = 0.4; + + public Sprinting sprinting = new Sprinting(); + public class Sprinting extends ConfigurationPart { + public boolean requireFullAttack = true; + public double extraKnockback = 0.5; + @Comment("Delay between extra knockback hits in milliseconds") + public IntOr.Default knockbackDelay = IntOr.Default.USE_DEFAULT; + } + + @NestedSetting({"projectiles", "fishing-hooks-apply-knockback"}) + public boolean fishingHooksApplyKnockback; + + @Comment("Knockback resistance attribute modifier") + public double knockbackResistanceModifier = 1.0; + @Comment("Received by attacking a shielded enemy") + public double shieldHitKnockback = 0.5; + } + + @Comment("Prevents players swimming using elytra or riptide to enter holes") + public boolean posesShrinkCollisionBox = true; + public boolean fishingHooksPullEntities = true; + } + + public Entity entity; + public class Entity extends ConfigurationPart { + @Comment("Only modify if you know what you're doing") + public boolean disableMobAi = false; + public boolean waterSensitivity = true; + public boolean instantDeathAnimation = false; + public boolean ironGolemsTakeFalldamage = false; + + public Items items = new Items(); + public class Items extends ConfigurationPart { + public List explosionResistantItems = List.of(); + } + + @Comment("Entity travel distance limits") + public Map, Integer> chunkTravelLimit = Util.make(new Reference2ObjectOpenHashMap<>(), map -> { + map.put(EntityType.ENDER_PEARL, 8); + }); + + public ThrownPotion thrownPotion = new ThrownPotion(); + public class ThrownPotion extends ConfigurationPart { + public double horizontalSpeed = 1.0; + public double verticalSpeed = 1.0; + public boolean allowBreakingInsideEntities = false; + } + + public EnderPearl enderPearl = new EnderPearl(); + public class EnderPearl extends ConfigurationPart { + public boolean useOutlineForCollision = false; + } + } + + public Environment environment; + public class Environment extends ConfigurationPart { + public boolean allowWaterInTheNether = false; + public boolean disableFastNetherLava = false; + + public BlockGeneration blockGeneration = new BlockGeneration(); + public class BlockGeneration extends ConfigurationPart { + public boolean legacyBlockFormation = false; + } + + public Crops crops = new Crops(); + public class Crops extends ConfigurationPart { + public boolean useRandomChanceToGrow = false; + } + + public MobSpawner mobSpawner = new MobSpawner(); + public class MobSpawner extends ConfigurationPart { + public boolean checkSpawnConditions = true; + public boolean requireNearbyPlayer = true; + public boolean ignoreEntityLimit = false; + } + } + +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/ConfigurationTransformations.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/ConfigurationTransformations.java new file mode 100644 index 0000000..154ab96 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/ConfigurationTransformations.java @@ -0,0 +1,46 @@ +package me.samsuik.sakura.configuration.transformation; + +import io.papermc.paper.configuration.transformation.Transformations; +import me.samsuik.sakura.configuration.transformation.global.V1_RelocateMessages; +import me.samsuik.sakura.configuration.transformation.global.V2_ConvertIconToMaterial; +import me.samsuik.sakura.configuration.transformation.world.*; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import java.util.List; + +public final class ConfigurationTransformations { + private static final List REMOVED_GLOBAL_PATHS = List.of( + NodePath.path("cannons") + ); + + public static void worldTransformations(final ConfigurationNode node) throws ConfigurateException { + final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder(); + V2_VerticalKnockbackUseDefault.apply(versionedBuilder); + V3_RenameKnockback.apply(versionedBuilder); + V4_RenameNonStrictMergeLevel.apply(versionedBuilder); + V5_CombineLoadChunksOptions.apply(versionedBuilder); + V6_FixIncorrectExtraKnockback.apply(versionedBuilder); + // ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE + versionedBuilder.build().apply(node); + } + + public static void globalTransformations(final ConfigurationNode node) throws ConfigurateException { + final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder(); + for (final NodePath path : REMOVED_GLOBAL_PATHS) { + builder.addAction(path, TransformAction.remove()); + } + builder.build().apply(node); + + final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder(); + V1_RelocateMessages.apply(versionedBuilder); + V2_ConvertIconToMaterial.apply(versionedBuilder); + // ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE + versionedBuilder.build().apply(node); + } + + private ConfigurationTransformations() {} +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V1_RelocateMessages.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V1_RelocateMessages.java new file mode 100644 index 0000000..6c72577 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V1_RelocateMessages.java @@ -0,0 +1,31 @@ +package me.samsuik.sakura.configuration.transformation.global; + +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import java.util.Map; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V1_RelocateMessages { + private static final int VERSION = 2; // targeted version is always ahead by one + private static final Map RELOCATION = Map.of( + path("fps", "message"), path("messages", "fps-setting-change"), + path("players", "potato-message"), path("messages", "durable-block-interaction") + ); + + private V1_RelocateMessages() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder(); + for (Map.Entry entry : RELOCATION.entrySet()) { + transformationBuilder.addAction(entry.getKey(), relocate(entry.getValue())); + } + builder.addVersion(VERSION, transformationBuilder.build()); + } + + private static TransformAction relocate(NodePath path) { + return (node, object) -> path.array(); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V2_ConvertIconToMaterial.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V2_ConvertIconToMaterial.java new file mode 100644 index 0000000..c6f8bfe --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/global/V2_ConvertIconToMaterial.java @@ -0,0 +1,28 @@ +package me.samsuik.sakura.configuration.transformation.global; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +public final class V2_ConvertIconToMaterial implements TransformAction { + private static final int VERSION = 3; // targeted version is always ahead by one + private static final NodePath PATH = NodePath.path("fps", "material"); + private static final V2_ConvertIconToMaterial INSTANCE = new V2_ConvertIconToMaterial(); + + private V2_ConvertIconToMaterial() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + if (value.raw() instanceof String stringValue) { + value.raw(stringValue.toUpperCase()); + } + return null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V2_VerticalKnockbackUseDefault.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V2_VerticalKnockbackUseDefault.java new file mode 100644 index 0000000..97d5224 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V2_VerticalKnockbackUseDefault.java @@ -0,0 +1,31 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import io.papermc.paper.configuration.type.number.DoubleOr; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V2_VerticalKnockbackUseDefault implements TransformAction { + private static final int VERSION = 2; + private static final NodePath PATH = path("players", "knockback", "knockback-vertical"); + private static final V2_VerticalKnockbackUseDefault INSTANCE = new V2_VerticalKnockbackUseDefault(); + + private V2_VerticalKnockbackUseDefault() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + if (value.getDouble() == 0.4) { + value.set(DoubleOr.Default.USE_DEFAULT); + } + return null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V3_RenameKnockback.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V3_RenameKnockback.java new file mode 100644 index 0000000..efba1f8 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V3_RenameKnockback.java @@ -0,0 +1,27 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; + +import java.util.Map; + +import static org.spongepowered.configurate.NodePath.path; +import static org.spongepowered.configurate.transformation.TransformAction.*; + +public final class V3_RenameKnockback { + private static final int VERSION = 3; + private static final Map RENAME = Map.of( + path("players", "knockback", "vertical-limit-require-ground"), "vertical-knockback-require-ground", + path("players", "knockback", "knockback-horizontal"), "base-knockback" + ); + + private V3_RenameKnockback() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder(); + for (Map.Entry entry : RENAME.entrySet()) { + transformationBuilder.addAction(entry.getKey(), rename(entry.getValue())); + } + builder.addVersion(VERSION, transformationBuilder.build()); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V4_RenameNonStrictMergeLevel.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V4_RenameNonStrictMergeLevel.java new file mode 100644 index 0000000..198eb5f --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V4_RenameNonStrictMergeLevel.java @@ -0,0 +1,35 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import java.util.Locale; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V4_RenameNonStrictMergeLevel implements TransformAction { + private static final int VERSION = 4; + private static final String OLD_LEVEL_NAME = "NON_STRICT"; + private static final String NEW_LEVEL_NAME = "LENIENT"; + private static final NodePath PATH = path("cannons", "merge-level"); + private static final V4_RenameNonStrictMergeLevel INSTANCE = new V4_RenameNonStrictMergeLevel(); + + private V4_RenameNonStrictMergeLevel() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + String level = value.getString(); + if (level != null && OLD_LEVEL_NAME.equals(level.toUpperCase(Locale.ENGLISH))) { + value.set(NEW_LEVEL_NAME); + } + return null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V5_CombineLoadChunksOptions.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V5_CombineLoadChunksOptions.java new file mode 100644 index 0000000..3d647a3 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V5_CombineLoadChunksOptions.java @@ -0,0 +1,44 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import java.util.List; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V5_CombineLoadChunksOptions implements TransformAction { + private static final int VERSION = 5; + private static final List ENTITY_PATHS = List.of("tnt", "sand"); + private static final String OLD_NAME = "loads-chunks"; + private static final String NAME = "load-chunks"; + private static final NodePath PATH = path("cannons"); + private static final V5_CombineLoadChunksOptions INSTANCE = new V5_CombineLoadChunksOptions(); + + private V5_CombineLoadChunksOptions() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + boolean shouldLoadChunks = false; + + for (String entity : ENTITY_PATHS) { + NodePath entityPath = NodePath.path(entity, OLD_NAME); + if (value.hasChild(entityPath)) { + ConfigurationNode node = value.node(entityPath); + shouldLoadChunks |= node.getBoolean(); + node.raw(null); + } + } + + value.node(NAME).set(shouldLoadChunks); + return null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V6_FixIncorrectExtraKnockback.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V6_FixIncorrectExtraKnockback.java new file mode 100644 index 0000000..f9fdf1a --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V6_FixIncorrectExtraKnockback.java @@ -0,0 +1,30 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V6_FixIncorrectExtraKnockback implements TransformAction { + private static final int VERSION = 6; + private static final NodePath PATH = path("players", "knockback", "sprinting", "extra-knockback"); + private static final V6_FixIncorrectExtraKnockback INSTANCE = new V6_FixIncorrectExtraKnockback(); + + private V6_FixIncorrectExtraKnockback() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + if (value.getDouble() == 1.0) { + value.set(0.5); + } + return null; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java new file mode 100644 index 0000000..4024f97 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java @@ -0,0 +1,7 @@ +package me.samsuik.sakura.explosion.durable; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public record DurableMaterial(int durability, float resistance) { +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java b/sakura-server/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java new file mode 100644 index 0000000..7de60ff --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java @@ -0,0 +1,203 @@ +package me.samsuik.sakura.local.config; + +import com.google.common.collect.Iterables; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import me.samsuik.sakura.local.LocalRegion; +import me.samsuik.sakura.local.storage.LocalStorageHandler; +import me.samsuik.sakura.local.storage.LocalValueStorage; +import me.samsuik.sakura.utils.TickExpiry; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.*; +import java.util.function.LongConsumer; + +public final class LocalConfigManager implements LocalStorageHandler { + private static final int SMALL_REGION_SIZE = 12; + private static final int CONFIG_CACHE_EXPIRATION = 600; + + private final Map storageMap = new Object2ObjectOpenHashMap<>(); + private final List largeRegions = new ObjectArrayList<>(); + private final Long2ObjectMap> smallRegions = new Long2ObjectOpenHashMap<>(); + private int regionExponent = 0; + private final Long2ObjectMap> chunkConfigCache = new Long2ObjectOpenHashMap<>(); + private final Level level; + private long expirationTick = 0L; + + public LocalConfigManager(Level level) { + this.level = level; + } + + @Override + public synchronized @NonNull Optional locate(int x, int z) { + int regionX = x >> this.regionExponent; + int regionZ = z >> this.regionExponent; + long regionPos = ChunkPos.asLong(regionX, regionZ); + List regions = this.smallRegions.get(regionPos); + for (LocalRegion region : Iterables.concat(regions, this.largeRegions)) { + if (region.contains(x, z)) { + return Optional.of(region); + } + } + return Optional.empty(); + } + + @Override + public synchronized @Nullable LocalValueStorage get(@NonNull LocalRegion region) { + return this.storageMap.get(region); + } + + @Override + public synchronized boolean has(@NonNull LocalRegion region) { + return this.storageMap.containsKey(region); + } + + @Override + public synchronized void put(@NonNull LocalRegion region, @NonNull LocalValueStorage storage) { + this.ensureNotOverlapping(region); + int shift = this.regionExponent; + int regionChunks = regionChunks(region, shift); + + if (regionChunks <= SMALL_REGION_SIZE) { + this.forEachRegionChunks(region, pos -> { + this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>()) + .add(region); + }); + } else { + this.largeRegions.add(region); + // The region exponent might be too small + if (this.largeRegions.size() % 24 == 0) { + this.resizeRegions(); + } + } + + this.chunkConfigCache.clear(); + this.storageMap.put(region, storage); + } + + @Override + public synchronized void remove(@NonNull LocalRegion region) { + this.forEachRegionChunks(region, pos -> { + List regions = this.smallRegions.get(pos); + if (regions != null) { + regions.remove(region); + if (regions.isEmpty()) { + this.smallRegions.remove(pos); + } + } + }); + + this.chunkConfigCache.clear(); + this.storageMap.remove(region); + } + + private void forEachRegionChunks(LocalRegion region, LongConsumer chunkConsumer) { + int exponent = this.regionExponent; + int minX = region.minX() >> exponent; + int minZ = region.minZ() >> exponent; + int maxX = region.maxX() >> exponent; + int maxZ = region.maxZ() >> exponent; + + for (int x = minX; x <= maxX; ++x) { + for (int z = minZ; z <= maxZ; ++z) { + chunkConsumer.accept(ChunkPos.asLong(x, z)); + } + } + } + + private void resizeRegions() { + List regions = this.regions(); + int newExponent = this.calculateRegionExponent(regions); + if (newExponent == this.regionExponent) { + return; // nothing has changed + } + + this.regionExponent = newExponent; + this.largeRegions.clear(); + this.smallRegions.clear(); + + for (LocalRegion region : regions) { + int regionChunks = regionChunks(region, newExponent); + if (regionChunks <= SMALL_REGION_SIZE) { + this.forEachRegionChunks(region, pos -> { + this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>()) + .add(region); + }); + } else { + this.largeRegions.add(region); + } + } + } + + private int calculateRegionExponent(List regions) { + int regionChunks = 0; + for (LocalRegion region : regions) { + regionChunks += regionChunks(region, 0); + } + regionChunks /= regions.size(); + + int exponent = 4; + while (true) { + if ((regionChunks >> exponent++) <= SMALL_REGION_SIZE / 2) { + return exponent; + } + } + } + + private static int regionChunks(LocalRegion region, int exponent) { + int sizeX = region.maxX() - region.minX() >> exponent; + int sizeZ = region.maxZ() - region.minZ() >> exponent; + return (sizeX + 1) * (sizeZ + 1); + } + + @Override + public synchronized @NonNull List regions() { + return new ArrayList<>(this.storageMap.keySet()); + } + + public synchronized LocalValueConfig config(BlockPos position) { + long gameTime = this.level.getGameTime(); + long ticks = this.expirationTick - gameTime; + if (ticks >= CONFIG_CACHE_EXPIRATION / 3) { + this.chunkConfigCache.values().removeIf(pair -> pair.value().isExpired(gameTime)); + this.expirationTick = gameTime; + } + + long chunkKey = ChunkPos.asLong(position.getX() >> 4, position.getZ() >> 4); + Pair pair = this.chunkConfigCache.computeIfAbsent(chunkKey, k -> { + return Pair.of(this.createLocalChunkConfig(position), new TickExpiry(gameTime, CONFIG_CACHE_EXPIRATION)); + }); + + pair.value().refresh(gameTime); + return pair.key(); + } + + private LocalValueConfig createLocalChunkConfig(BlockPos position) { + // uses defaults from the sakura config + LocalValueConfig config = new LocalValueConfig(this.level); + this.locate(position.getX(), position.getZ()).ifPresent(region -> { + config.loadFromStorage(this.storageMap.get(region)); + }); + return config; + } + + private void ensureNotOverlapping(LocalRegion region) { + Set nearbyRegions = new ReferenceOpenHashSet<>(); + this.forEachRegionChunks(region, pos -> { + nearbyRegions.addAll(this.smallRegions.getOrDefault(pos, List.of())); + }); + for (LocalRegion present : Iterables.concat(nearbyRegions, this.largeRegions)) { + if (present != region && present.intersects(region)) { + throw new UnsupportedOperationException("overlapping region (%s, %s)".formatted(present, region)); + } + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java b/sakura-server/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java new file mode 100644 index 0000000..4c9758f --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java @@ -0,0 +1,47 @@ +package me.samsuik.sakura.local.config; + +import io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import me.samsuik.sakura.explosion.durable.DurableMaterial; +import me.samsuik.sakura.local.LocalValueKeys; +import me.samsuik.sakura.local.storage.LocalValueStorage; +import me.samsuik.sakura.physics.PhysicsVersion; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +import java.util.Map; + +public final class LocalValueConfig { + public Map durableMaterials; + public RedstoneImplementation redstoneImplementation; + public PhysicsVersion physicsVersion; + public boolean consistentRadius; + public boolean redstoneCache; + public int lavaFlowSpeed = -1; + + LocalValueConfig(Level level) { + this.durableMaterials = new Reference2ObjectOpenHashMap<>(level.sakuraConfig().cannons.explosion.durableMaterials); + this.redstoneImplementation = level.paperConfig().misc.redstoneImplementation; + this.physicsVersion = level.sakuraConfig().cannons.mechanics.physicsVersion; + this.consistentRadius = level.sakuraConfig().cannons.explosion.consistentRadius; + this.redstoneCache = level.sakuraConfig().technical.redstone.redstoneCache; + } + + void loadFromStorage(LocalValueStorage storage) { + storage.get(LocalValueKeys.DURABLE_MATERIALS).ifPresent(materials -> { + materials.forEach((materialType, materialProperties) -> { + Block nmsBlock = CraftMagicNumbers.getBlock(materialType); + DurableMaterial durableMaterial = new DurableMaterial(materialProperties.getKey(), materialProperties.getValue()); + this.durableMaterials.put(nmsBlock, durableMaterial); + }); + }); + storage.get(LocalValueKeys.REDSTONE_IMPLEMENTATION).ifPresent(implementation -> { + this.redstoneImplementation = RedstoneImplementation.values()[implementation.ordinal()]; + }); + this.physicsVersion = storage.getOrDefault(LocalValueKeys.PHYSICS_VERSION, this.physicsVersion); + this.consistentRadius = storage.getOrDefault(LocalValueKeys.CONSISTENT_EXPLOSION_RADIUS, this.consistentRadius); + this.redstoneCache = storage.getOrDefault(LocalValueKeys.REDSTONE_CACHE, this.redstoneCache); + this.lavaFlowSpeed = storage.getOrDefault(LocalValueKeys.LAVA_FLOW_SPEED, this.lavaFlowSpeed); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java b/sakura-server/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java new file mode 100644 index 0000000..e0aa768 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java @@ -0,0 +1,64 @@ +package me.samsuik.sakura.player.combat; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.ItemAttributeModifiers; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.item.enchantment.ItemEnchantments; +import org.apache.commons.lang3.mutable.MutableFloat; + +import java.util.OptionalDouble; + +public final class CombatUtil { + public static double getLegacyAttackDifference(ItemStack itemstack) { + ItemAttributeModifiers defaultModifiers = itemstack.getItem().components().get(DataComponents.ATTRIBUTE_MODIFIERS); + if (defaultModifiers != null && !defaultModifiers.modifiers().isEmpty()) { // exists + double baseAttack = 0.0; + for (ItemAttributeModifiers.Entry entry : defaultModifiers.modifiers()) { + if (!entry.slot().test(EquipmentSlot.MAINHAND) || !entry.attribute().is(Attributes.ATTACK_DAMAGE)) + continue; + if (entry.modifier().operation() != AttributeModifier.Operation.ADD_VALUE) + return 0; + baseAttack += entry.modifier().amount(); + } + + OptionalDouble legacyAttack = LegacyDamageMapping.itemAttackDamage(itemstack.getItem()); + if (baseAttack != 0.0 && legacyAttack.isPresent()) { + return legacyAttack.getAsDouble() - baseAttack; + } + } + return 0; + } + + public static float calculateLegacySharpnessDamage(LivingEntity entity, ItemStack itemstack, DamageSource damageSource) { + Holder enchantment = getEnchantmentHolder(Enchantments.SHARPNESS); + ItemEnchantments itemEnchantments = itemstack.getEnchantments(); + int enchantmentLevel = itemEnchantments.getLevel(enchantment); + MutableFloat damage = new MutableFloat(); + + if (entity.level() instanceof ServerLevel level) { + enchantment.value().modifyDamage(level, enchantmentLevel, itemstack, entity, damageSource, damage); + } + // legacy - modern + return enchantmentLevel * 1.25F - damage.getValue(); + } + + private static Holder getEnchantmentHolder(ResourceKey enchantmentKey) { + RegistryAccess registryAccess = MinecraftServer.getServer().registryAccess(); + HolderLookup.RegistryLookup enchantments = registryAccess.lookupOrThrow(Registries.ENCHANTMENT); + return enchantments.getOrThrow(enchantmentKey); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/player/combat/CustomGoldenApple.java b/sakura-server/src/main/java/me/samsuik/sakura/player/combat/CustomGoldenApple.java new file mode 100644 index 0000000..18cb28a --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/player/combat/CustomGoldenApple.java @@ -0,0 +1,64 @@ +package me.samsuik.sakura.player.combat; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.Consumable; +import net.minecraft.world.item.component.Consumables; +import net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect; +import net.minecraft.world.level.Level; +import org.jspecify.annotations.NullMarked; + +import java.util.List; +import java.util.Optional; + +@NullMarked +@SuppressWarnings("OptionalAssignedToNull") +public final class CustomGoldenApple extends Item { + private static final Consumable LEGACY_ENCHANTED_GOLDEN_APPLE = Consumables.defaultFood() + .onConsume( + new ApplyStatusEffectsConsumeEffect( + List.of( + new MobEffectInstance(MobEffects.REGENERATION, 600, 4), + new MobEffectInstance(MobEffects.DAMAGE_RESISTANCE, 6000, 0), + new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 6000, 0), + new MobEffectInstance(MobEffects.ABSORPTION, 2400, 0) + ) + ) + ) + .build(); + + public CustomGoldenApple(Properties settings) { + super(settings); + } + + @Override + public InteractionResult use(Level level, Player player, InteractionHand hand) { + ItemStack stack = player.getItemInHand(hand); + if (this.itemHasConsumableComponent(stack, level)) { + return super.use(level, player, hand); + } else { + return LEGACY_ENCHANTED_GOLDEN_APPLE.startConsuming(player, stack, hand); + } + } + + @Override + public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity entity) { + if (this.itemHasConsumableComponent(stack, level)) { + return super.finishUsingItem(stack, level, entity); + } else { + return LEGACY_ENCHANTED_GOLDEN_APPLE.onConsume(level, entity, stack); + } + } + + private boolean itemHasConsumableComponent(ItemStack stack, Level level) { + Optional consumable = stack.getComponentsPatch().get(DataComponents.CONSUMABLE); + return consumable != null || !level.sakuraConfig().players.combat.oldEnchantedGoldenApple; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java b/sakura-server/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java new file mode 100644 index 0000000..b4ab8f1 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java @@ -0,0 +1,64 @@ +package me.samsuik.sakura.player.combat; + +import it.unimi.dsi.fastutil.objects.Reference2DoubleMap; +import it.unimi.dsi.fastutil.objects.Reference2DoubleOpenHashMap; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.ItemAttributeModifiers; + +import java.util.OptionalDouble; + +public final class LegacyDamageMapping { + private static final Reference2DoubleMap LEGACY_ITEM_DAMAGE_MAP = new Reference2DoubleOpenHashMap<>(); + + public static OptionalDouble itemAttackDamage(Item item) { + double result = LEGACY_ITEM_DAMAGE_MAP.getDouble(item); + return result == Double.MIN_VALUE ? OptionalDouble.empty() : OptionalDouble.of(result); + } + + private static double adjustDamageForItem(Item item, double attackDamage) { + return switch (item) { + case SwordItem i -> 1.0; + case PickaxeItem i -> 1.0; + case ShovelItem i -> -0.5; + case HoeItem i -> -attackDamage; + case null, default -> 0.0; + }; + } + + static { + LEGACY_ITEM_DAMAGE_MAP.defaultReturnValue(Double.MIN_VALUE); + + // tool material is no longer exposed + LEGACY_ITEM_DAMAGE_MAP.put(Items.WOODEN_AXE, 3.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.GOLDEN_AXE, 3.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.STONE_AXE, 4.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.IRON_AXE, 5.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.DIAMOND_AXE, 6.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.NETHERITE_AXE, 7.0); + + for (Item item : BuiltInRegistries.ITEM) { + ItemAttributeModifiers modifiers = item.components().get(DataComponents.ATTRIBUTE_MODIFIERS); + + if (modifiers == null || LEGACY_ITEM_DAMAGE_MAP.containsKey(item)) { + continue; + } + + assert item instanceof AxeItem : "missing axe mapping"; + + double attackDamage = modifiers.modifiers().stream() + .filter(e -> e.attribute().is(Attributes.ATTACK_DAMAGE)) + .mapToDouble(e -> e.modifier().amount()) + .sum(); + + if (attackDamage > 0.0) { + double adjustment = adjustDamageForItem(item, attackDamage); + LEGACY_ITEM_DAMAGE_MAP.put(item, attackDamage + adjustment); + } + } + } + + private LegacyDamageMapping() {} +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/tps/ServerTickInformation.java b/sakura-server/src/main/java/me/samsuik/sakura/tps/ServerTickInformation.java new file mode 100644 index 0000000..20d88bf --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/tps/ServerTickInformation.java @@ -0,0 +1,36 @@ +package me.samsuik.sakura.tps; + +import me.samsuik.sakura.configuration.GlobalConfiguration; +import me.samsuik.sakura.tps.graph.GraphComponents; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.TextColor; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record ServerTickInformation(long identifier, double tps, double averageTick, long longestTick, float targetTickRate, int chunks, int entities) { + public static final ServerTickInformation FILLER = new ServerTickInformation(0, 0.0, 0.0, 0, 0.0f, 0, 0); + + public TextColor colour() { + float lag = (float) this.tps / this.targetTickRate; + return GraphComponents.colour(lag); + } + + public Component hoverComponent(TextColor colour) { + TextComponent.Builder builder = Component.text(); + builder.append(Component.text("TPS: ") + .append(Component.text("%.1f".formatted(this.tps), colour))); + builder.appendNewline(); + builder.append(Component.text("MSPT: ") + .append(Component.text("%.1f".formatted(this.averageTick), colour)) + .append(Component.text("/")) + .append(Component.text(this.longestTick, colour))); + if (GlobalConfiguration.get().messages.tpsShowEntityAndChunkCount) { + builder.appendNewline(); + builder.append(Component.text("Entities: " + this.entities)); + builder.appendNewline(); + builder.append(Component.text("Chunks: " + this.chunks)); + } + return builder.build(); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/tps/TickInformationCollector.java b/sakura-server/src/main/java/me/samsuik/sakura/tps/TickInformationCollector.java new file mode 100644 index 0000000..5f88e17 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/tps/TickInformationCollector.java @@ -0,0 +1,71 @@ +package me.samsuik.sakura.tps; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import org.jspecify.annotations.NullMarked; + +import java.util.Collection; +import java.util.List; + +@NullMarked +public final class TickInformationCollector { + private static final int TEN_MINUTES = 10 * 60; + private final ObjectArrayList collectedInformation = new ObjectArrayList<>(); + private final LongArrayList tickSamples = new LongArrayList(); + private long identifier = 0; + + public ServerTickInformation latestTickInformation() { + return this.collectedInformation.getLast(); + } + + public void levelData(Collection levels, double tps) { + int chunks = 0; + int entities = 0; + for (ServerLevel level : levels) { + chunks += level.chunkSource.getFullChunksCount(); + entities += level.entityTickList.entities.size(); + } + + double averageTick = this.tickSamples.longStream() + .average() + .orElse(0.0); + long longestTick = this.tickSamples.longStream() + .max() + .orElse(0); + float targetTickRate = MinecraftServer.getServer().tickRateManager().tickrate(); + + ServerTickInformation tickInformation = new ServerTickInformation( + this.identifier++, tps, averageTick, longestTick, targetTickRate, chunks, entities + ); + + this.collectedInformation.add(tickInformation); + this.tickSamples.clear(); + + if (this.collectedInformation.size() > TEN_MINUTES) { + this.collectedInformation.subList(0, 60).clear(); + } + } + + public void tickDuration(long timeTaken) { + this.tickSamples.add(timeTaken); + } + + public ImmutableList collect(long from, long to) { + List collected = new ObjectArrayList<>(); + for (ServerTickInformation tickInformation : this.collectedInformation.reversed()) { + if (tickInformation.identifier() >= from && tickInformation.identifier() < to) { + collected.add(tickInformation); + } + } + long ahead = to - this.identifier; + long missing = to - from - collected.size(); + for (int i = 0; i < missing; ++i) { + int ind = (i < ahead) ? 0 : collected.size(); + collected.add(ind, ServerTickInformation.FILLER); + } + return ImmutableList.copyOf(collected); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/BuiltComponentCanvas.java b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/BuiltComponentCanvas.java new file mode 100644 index 0000000..bf3e953 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/BuiltComponentCanvas.java @@ -0,0 +1,36 @@ +package me.samsuik.sakura.tps.graph; + +import com.google.common.collect.ImmutableList; +import net.kyori.adventure.text.Component; +import org.jspecify.annotations.NullMarked; + +import java.util.List; + +@NullMarked +public final class BuiltComponentCanvas { + private final List components; + + BuiltComponentCanvas(List components) { + this.components = components; + } + + public void appendLeft(Component component) { + this.components.replaceAll(component::append); + } + + public void appendRight(Component component) { + this.components.replaceAll(row -> row.append(component)); + } + + public void header(Component component) { + this.components.addFirst(component); + } + + public void footer(Component component) { + this.components.add(component); + } + + public ImmutableList components() { + return ImmutableList.copyOf(this.components); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/ComponentCanvas.java b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/ComponentCanvas.java new file mode 100644 index 0000000..4202465 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/ComponentCanvas.java @@ -0,0 +1,63 @@ +package me.samsuik.sakura.tps.graph; + +import com.google.common.base.Preconditions; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import org.jspecify.annotations.NullMarked; + +import java.util.List; + +@NullMarked +public final class ComponentCanvas { + private final int width; + private final int height; + private final Component[][] components; + + public ComponentCanvas(int width, int height) { + this.width = width; + this.height = height; + // [x, y] is flipped as it makes converting the components into a list easier + this.components = new Component[height][width]; + } + + public void flip() { + for (int y = 0; y < this.height; ++y) { + if (y >= this.height / 2) { + Component[] row = this.components[y]; + int relocatingRow = this.height - 1 - y; + this.components[y] = this.components[relocatingRow]; + this.components[relocatingRow] = row; + } + } + } + + public void fill(Component component) { + for (int x = 0; x < this.width; ++x) { + for (int y = 0; y < this.height; ++y) { + this.set(x, y, component); + } + } + } + + public Component get(int x, int y) { + Component component = this.components[y][x]; + return Preconditions.checkNotNull(component, "missing component at x:{} y:{}", x, y); + } + + public void set(int x, int y, Component component) { + this.components[y][x] = component; + } + + public BuiltComponentCanvas build() { + return new BuiltComponentCanvas(this.joinComponents()); + } + + private List joinComponents() { + List componentList = new ObjectArrayList<>(this.height); + for (Component[] row : this.components) { + componentList.add(Component.join(JoinConfiguration.noSeparators(), row)); + } + return componentList; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/DetailedTPSGraph.java b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/DetailedTPSGraph.java new file mode 100644 index 0000000..bebc61d --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/DetailedTPSGraph.java @@ -0,0 +1,102 @@ +package me.samsuik.sakura.tps.graph; + +import me.samsuik.sakura.tps.ServerTickInformation; +import org.jspecify.annotations.NullMarked; + +import java.util.List; + +@NullMarked +public final class DetailedTPSGraph extends TPSGraph { + public DetailedTPSGraph(int width, int height, double scale, List tickInformation) { + super(width, height, scale, tickInformation); + } + + @Override + public BuiltComponentCanvas plot() { + ComponentCanvas canvas = new ComponentCanvas(this.width, this.height); + canvas.fill(GraphComponents.BACKGROUND); + + this.basicOutline(canvas); + this.prettifyOutline(canvas); + this.addColourAndHoverInformation(canvas); + + canvas.flip(); + return canvas.build(); + } + + private void basicOutline(ComponentCanvas canvas) { + for (int x = 0; x < this.width; ++x) { + int row = this.rowFromColumn(x); + int nextRow = this.rowFromColumn(x + 1); + int minRow = Math.min(row, nextRow); + int maxRow = Math.max(row, nextRow); + + if (maxRow - minRow >= 2) { + canvas.set(x, minRow, GraphComponents.TOP_DOTTED_LINE); + canvas.set(x, maxRow, GraphComponents.BOTTOM_DOTTED_LINE); + + for (int y = minRow + 1; y < maxRow; ++y) { + canvas.set(x, y, GraphComponents.VERTICAL_LINE); + } + } else { + canvas.set(x, row, GraphComponents.HORIZONTAL_LINE); + } + } + } + + private void prettifyOutline(ComponentCanvas canvas) { + for (int x = 0; x < this.width; ++x) { + int row = this.rowFromColumn(x); + int nextRow = this.rowFromColumn(x + 1); + int prevRow = this.rowFromColumn(x - 1); + int minRow = Math.min(row, nextRow); + int maxRow = Math.max(row, nextRow); + + if (maxRow - minRow >= 2) { + this.prettifyVerticalOutline(canvas, x, row, nextRow, prevRow, minRow, maxRow); + } else { + this.prettifySlopes(canvas, x, row, nextRow, prevRow); + } + } + } + + private void prettifyVerticalOutline(ComponentCanvas canvas, int x, int row, int nextRow, int prevRow, int minRow, int maxRow) { + if (minRow == nextRow) { + canvas.set(x, minRow, GraphComponents.CONE_BOTTOM_LEFT); + } else if (prevRow <= minRow) { + canvas.set(x, minRow, GraphComponents.CONE_BOTTOM_RIGHT); + } + if (prevRow == row + 1 && nextRow < row) { + canvas.set(x, maxRow, GraphComponents.CONE_TOP_RIGHT); + } + if (maxRow == row && Math.abs(nextRow - maxRow) > 1 && Math.abs(prevRow - maxRow) > 1 && prevRow < maxRow) { + canvas.set(x - 1, maxRow, GraphComponents.CONE_TOP_LEFT); + canvas.set(x, maxRow, GraphComponents.CONE_TOP_RIGHT); + } + if (minRow == row && Math.abs(nextRow - minRow) > 1 && Math.abs(prevRow - minRow) > 1 && prevRow > minRow) { + canvas.set(x - 1, minRow, GraphComponents.CONE_BOTTOM_LEFT); + canvas.set(x, minRow, GraphComponents.CONE_BOTTOM_RIGHT); + } + } + + private void prettifySlopes(ComponentCanvas canvas, int x, int row, int nextRow, int prevRow) { + int slopeDirection = nextRow - prevRow; + int slopeChange = Math.abs(slopeDirection); + + if (slopeChange >= 2 && Math.max(nextRow, prevRow) == row + 1) { + canvas.set(x, row, slopeDirection < 0 ? GraphComponents.TL_TO_BR : GraphComponents.BL_TO_TR); + } else if (Math.abs(row - nextRow) == 1 || slopeDirection == 0) { + if (row < nextRow) { + canvas.set(x, row, GraphComponents.TOP_DOTTED_LINE); + } else if (row > nextRow) { + canvas.set(x, row, GraphComponents.BOTTOM_DOTTED_LINE); + } + } else if (Math.abs(row - prevRow) == 1) { + if (prevRow > row) { + canvas.set(x, row, GraphComponents.TOP_DOTTED_LINE); + } else if (prevRow < row) { + canvas.set(x, row, GraphComponents.BOTTOM_DOTTED_LINE); + } + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/GraphComponents.java b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/GraphComponents.java new file mode 100644 index 0000000..95d3c80 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/GraphComponents.java @@ -0,0 +1,42 @@ +package me.samsuik.sakura.tps.graph; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; + +import java.util.List; + +public final class GraphComponents { + private static final Style STRIKE_THROUGH_STYLE = Style.style(TextDecoration.STRIKETHROUGH); + private static final Style REMOVE_STRIKE_THROUGH_STYLE = Style.style(TextDecoration.STRIKETHROUGH.withState(false)); + + public static final Component BACKGROUND = Component.text("::"); + public static final Component HORIZONTAL_LINE = Component.text(" ", STRIKE_THROUGH_STYLE); + public static final Component VERTICAL_LINE = Component.text("||"); + public static final Component TOP_DOTTED_LINE = Component.text("''"); + public static final Component BOTTOM_DOTTED_LINE = Component.text(".."); + public static final Component BL_TO_TR = Component.text(".", STRIKE_THROUGH_STYLE).append(Component.text("'", REMOVE_STRIKE_THROUGH_STYLE)); + public static final Component TL_TO_BR = Component.text("'").append(Component.text(".", STRIKE_THROUGH_STYLE)); + public static final Component CONE_TOP_LEFT = Component.text(".!"); + public static final Component CONE_TOP_RIGHT = Component.text("!."); + public static final Component CONE_BOTTOM_LEFT = Component.text("'!"); + public static final Component CONE_BOTTOM_RIGHT = Component.text("!'"); + + private static final List COLOURS = List.of( + NamedTextColor.GREEN, NamedTextColor.YELLOW, NamedTextColor.GOLD, + NamedTextColor.RED, NamedTextColor.DARK_GRAY, TextColor.color(40, 40, 40) + ); + + public static TextColor colour(float num) { + float segment = 1.0f / COLOURS.size(); + float a = (1.0f - num) / segment; + float t = a % 1.0f; + int startIndex = Math.clamp((int) a, 0, COLOURS.size() - 2); + int endIndex = startIndex + 1; + TextColor startColour = COLOURS.get(startIndex); + TextColor endColour = COLOURS.get(endIndex); + return TextColor.lerp(t, startColour, endColour); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/TPSGraph.java b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/TPSGraph.java new file mode 100644 index 0000000..27b6b07 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/tps/graph/TPSGraph.java @@ -0,0 +1,60 @@ +package me.samsuik.sakura.tps.graph; + +import com.google.common.base.Preconditions; +import me.samsuik.sakura.tps.ServerTickInformation; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.minecraft.util.Mth; +import org.jspecify.annotations.NullMarked; + +import java.util.List; + +@NullMarked +public abstract class TPSGraph { + protected final List tickInformation; + protected final int width; + protected final int height; + protected final double scale; + + public TPSGraph(int width, int height, double scale, List tickInformation) { + Preconditions.checkArgument(tickInformation.size() == width); + this.width = width; + this.height = height; + this.scale = scale; + this.tickInformation = tickInformation; + } + + public abstract BuiltComponentCanvas plot(); + + protected final int rowFromColumn(int x) { + int clamped = Math.clamp(x, 0, this.width - 1); + ServerTickInformation tickInformation = this.tickInformation.get(clamped); + return this.rowFromTPS(tickInformation.tps()); + } + + protected final int rowFromTPS(double tps) { + int row = Mth.floor((tps / 3) * this.scale); + return Mth.clamp(row, 0, this.height - 1); + } + + protected final void addColourAndHoverInformation(ComponentCanvas canvas) { + for (int x = 0; x < this.width; ++x) { + ServerTickInformation tickInformation = this.tickInformation.get(x); + TextColor colourFromTPS = tickInformation.colour(); + Component hoverComponent = tickInformation.hoverComponent(colourFromTPS); + HoverEvent hoverEvent = HoverEvent.showText(hoverComponent); + + for (int y = 0; y < this.height; ++y) { + Component component = canvas.get(x, y); + if (component == GraphComponents.BACKGROUND) { + component = component.color(NamedTextColor.BLACK); + } else { + component = component.color(colourFromTPS); + } + canvas.set(x, y, component.hoverEvent(hoverEvent)); + } + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/utils/BlockPosIterator.java b/sakura-server/src/main/java/me/samsuik/sakura/utils/BlockPosIterator.java new file mode 100644 index 0000000..e00c07c --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/utils/BlockPosIterator.java @@ -0,0 +1,60 @@ +package me.samsuik.sakura.utils; + +import com.google.common.collect.AbstractIterator; +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public final class BlockPosIterator extends AbstractIterator { + private final int startX; + private final int startY; + private final int startZ; + private final int endX; + private final int endY; + private final int endZ; + private @Nullable MutableBlockPos pos = null; + + public static Iterable iterable(AABB bb) { + return () -> new BlockPosIterator(bb); + } + + public BlockPosIterator(AABB bb) { + this.startX = Mth.floor(bb.minX); + this.startY = Mth.floor(bb.minY); + this.startZ = Mth.floor(bb.minZ); + this.endX = Mth.floor(bb.maxX); + this.endY = Mth.floor(bb.maxY); + this.endZ = Mth.floor(bb.maxZ); + } + + @Override + protected BlockPos computeNext() { + MutableBlockPos pos = this.pos; + if (pos == null) { + return this.pos = new MutableBlockPos(this.startX, this.startY, this.startZ); + } else { + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); + + if (y < this.endY) { + y += 1; + } else if (x < this.endX) { + x += 1; + y = this.startY; + } else if (z < this.endZ) { + z += 1; + x = this.startX; + } else { + return this.endOfData(); + } + + pos.set(x, y, z); + return pos; + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/utils/TickExpiry.java b/sakura-server/src/main/java/me/samsuik/sakura/utils/TickExpiry.java new file mode 100644 index 0000000..22f1b53 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/utils/TickExpiry.java @@ -0,0 +1,24 @@ +package me.samsuik.sakura.utils; + +import com.google.common.base.Preconditions; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class TickExpiry { + private long tick; + private final int expiration; + + public TickExpiry(long tick, int expiration) { + Preconditions.checkArgument(expiration > 0, "expiration must be greater than 0"); + this.tick = tick; + this.expiration = expiration; + } + + public void refresh(long tick) { + this.tick = tick; + } + + public boolean isExpired(long tick) { + return this.tick >= tick - this.expiration; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/FixedSizeCustomObjectTable.java b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/FixedSizeCustomObjectTable.java new file mode 100644 index 0000000..e97f3cc --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/FixedSizeCustomObjectTable.java @@ -0,0 +1,54 @@ +package me.samsuik.sakura.utils.collections; + +import it.unimi.dsi.fastutil.HashCommon; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.ToIntFunction; + +public final class FixedSizeCustomObjectTable { + private final ToIntFunction keyFunction; + private final T[] contents; + private final int mask; + + public FixedSizeCustomObjectTable(int size, @NotNull ToIntFunction keyFunction) { + if (size < 0) { + throw new IllegalArgumentException("Table size cannot be negative"); + } else { + int n = HashCommon.nextPowerOfTwo(size - 1); + this.keyFunction = keyFunction; + this.contents = (T[]) new Object[n]; + this.mask = (n - 1); + } + } + + private int key(T value) { + return this.keyFunction.applyAsInt(value); + } + + public @Nullable T get(T value) { + return this.get(this.key(value)); + } + + public @Nullable T get(int key) { + return this.contents[key & this.mask]; + } + + public void write(int key, T value) { + this.contents[key & this.mask] = value; + } + + public @Nullable T getAndWrite(T value) { + int key = this.key(value); + T found = this.get(key); + this.write(key, value); + return found; + } + + public void clear() { + int size = this.contents.length; + for (int i = 0; i < size; ++i) { + this.contents[i] = null; + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java new file mode 100644 index 0000000..edbbf9d --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/OrderedComparatorList.java @@ -0,0 +1,49 @@ +package me.samsuik.sakura.utils.collections; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.Arrays; +import java.util.Comparator; + +public final class OrderedComparatorList extends ObjectArrayList { + private final Comparator comparator; + private boolean binarySearch = true; + + public OrderedComparatorList(int capacity, Comparator comparator) { + super(capacity); + this.comparator = Comparator.nullsLast(comparator); + } + + public OrderedComparatorList(Comparator comparator) { + this(DEFAULT_INITIAL_CAPACITY, comparator); + } + + private void validateBounds(int index, T t, boolean up) { + if (index != 0 && this.comparator.compare(get(index - 1), t) > 0) { + this.binarySearch = false; + } else if (up && index < size() - 1 && this.comparator.compare(get(index + 1), t) < 0) { + this.binarySearch = false; + } + } + + @Override + public boolean add(T t) { + this.validateBounds(size(), t, false); + return super.add(t); + } + + @Override + public void add(int index, T t) { + this.validateBounds(index, t, true); + super.add(index, t); + } + + @Override + public int indexOf(final Object k) { + if (this.binarySearch) { + return Math.max(Arrays.binarySearch(this.a, (T) k, this.comparator), -1); + } else { + return super.indexOf(k); + } + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java new file mode 100644 index 0000000..f267cd5 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/TrackedEntityChunkMap.java @@ -0,0 +1,32 @@ +package me.samsuik.sakura.utils.collections; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import net.minecraft.server.level.ChunkMap; + +public final class TrackedEntityChunkMap extends Int2ObjectOpenHashMap { + private final ObjectArrayList entityList = new UnorderedIndexedList<>(); + + @Override + public ChunkMap.TrackedEntity put(int k, ChunkMap.TrackedEntity trackedEntity) { + ChunkMap.TrackedEntity tracked = super.put(k, trackedEntity); + if (tracked != null) { + this.entityList.remove(trackedEntity); + } + this.entityList.add(trackedEntity); + return tracked; + } + + @Override + public ChunkMap.TrackedEntity remove(int k) { + ChunkMap.TrackedEntity tracked = super.remove(k); + this.entityList.remove(tracked); + return tracked; + } + + @Override + public ObjectCollection values() { + return this.entityList; + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java new file mode 100644 index 0000000..131dc69 --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/utils/collections/UnorderedIndexedList.java @@ -0,0 +1,61 @@ +package me.samsuik.sakura.utils.collections; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +public final class UnorderedIndexedList extends ObjectArrayList { + private final Int2IntOpenHashMap elementToIndex; + + public UnorderedIndexedList() { + this(DEFAULT_INITIAL_CAPACITY); + } + + public UnorderedIndexedList(int capacity) { + super(capacity); + this.elementToIndex = new Int2IntOpenHashMap(); + this.elementToIndex.defaultReturnValue(-1); + } + + @Override + public boolean add(final T t) { + this.elementToIndex.put(t.hashCode(), size()); + return super.add(t); + } + + @Override + public T remove(final int index) { + final int tail = size() - 1; + final T at = a[index]; + + if (index != tail) { + final T tailObj = a[tail]; + if (tailObj != null) + this.elementToIndex.put(tailObj.hashCode(), index); + this.a[index] = tailObj; + } + + if (at != null) + this.elementToIndex.remove(at.hashCode()); + this.a[tail] = null; + this.size = tail; + return at; + } + + @Override + public void clear() { + this.elementToIndex.clear(); + super.clear(); + } + + @Override + public int indexOf(final Object k) { + if (k == null) return -1; + // entities uses their id as a hashcode + return this.elementToIndex.get(k.hashCode()); + } + + @Override + public void add(final int index, final T t) { + throw new UnsupportedOperationException(); + } +} diff --git a/sakura-server/src/minecraft/java b/sakura-server/src/minecraft/java index 9221a6a..7a738b2 160000 --- a/sakura-server/src/minecraft/java +++ b/sakura-server/src/minecraft/java @@ -1 +1 @@ -Subproject commit 9221a6af363698f8da6174661aa7c28cdfd98cf8 +Subproject commit 7a738b2575321ce50cc91c4782cc336e9174f611 diff --git a/sakura-server/src/minecraft/resources b/sakura-server/src/minecraft/resources index 5a4f038..e35daf6 160000 --- a/sakura-server/src/minecraft/resources +++ b/sakura-server/src/minecraft/resources @@ -1 +1 @@ -Subproject commit 5a4f0388221bf0894ec2019f8b8132039a0f759a +Subproject commit e35daf625db43b9f5ace619d51fda5a104aa9f3e