From 3f889f504074cf14ab2f8d9464ebef4254d722dc Mon Sep 17 00:00:00 2001 From: Luna <90072930+LunaWasFlaggedAgain@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:15:48 -0300 Subject: [PATCH 01/10] Slime (#136) * Slime * Add proper credits. Thanks ASP! * Cleanup --- .gitmodules | 3 + README.md | 4 +- aswm-api/build.gradle.kts | 17 + aswm-api/src | 1 + aswm-core/build.gradle.kts | 7 + aswm-core/src | 1 + build.gradle.kts | 1 + .../0001-AdvancedSlimePaper-API-Changes.patch | 33 + ...ch => 0002-Add-MasterBlockFireEvent.patch} | 2 +- ...> 0003-Add-spectator-teleport-event.patch} | 2 +- ...004-Add-Scissors-configuration-file.patch} | 0 ...Add-command-block-player-edit-event.patch} | 0 ...01-AdvancedSlimePaper-Server-Changes.patch | 2128 +++++++++++++++++ ...changes.patch => 0002-Build-changes.patch} | 16 +- ...ation.patch => 0003-UUID-validation.patch} | 0 ...=> 0004-ResourceLocation-validation.patch} | 2 +- ...-Fixes-the-Blank-SkullOwner-exploit.patch} | 0 ...used-by-invalid-entities-in-beehive.patch} | 0 ...emoves-useless-spammy-error-logging.patch} | 0 ...own-when-trying-to-remove-minecart-.patch} | 0 ...if-items-are-air-before-calling-set.patch} | 0 ...ooks-causing-log-spam-when-invalid-.patch} | 0 ...te-BlockState-and-SoundEvent-values.patch} | 0 ...d-items-in-HoverEvent-and-ItemFrame.patch} | 0 ... 0013-Change-version-fetcher-to-AMG.patch} | 2 +- ...handling-of-invalid-JSON-components.patch} | 0 ...=> 0015-Block-server-side-chunkbans.patch} | 0 ...-oversized-components-from-updating.patch} | 0 ...Scissors-configuration-file-command.patch} | 0 ...s-with-invalid-namespaces-from-bein.patch} | 0 ...ry-player-data-in-the-nbt-component.patch} | 0 ...tring-tag-visitors-to-1024-elements.patch} | 0 ...lling-potion-effects-and-certain-po.patch} | 0 ...ch => 0022-Fix-negative-death-times.patch} | 0 ...ehicle-collision-checks-to-3-and-di.patch} | 0 ...Add-custom-classes-used-by-Scissors.patch} | 0 ...tags.patch => 0025-Reset-large-tags.patch} | 0 ...-Don-t-log-invalid-teams-to-console.patch} | 0 ...-bounds-HangingEntity-crash-exploit.patch} | 0 ...ch => 0028-Add-MasterBlockFireEvent.patch} | 0 ...> 0029-Add-spectator-teleport-event.patch} | 0 ...30-Prevent-invalid-container-events.patch} | 0 ...unning-commands-in-books-by-default.patch} | 0 ...k-entity-entity-tag-query-positions.patch} | 0 ...ents-on-Signs-bypassing-permissions.patch} | 2 +- ...-legacy-messages-over-1k-characters.patch} | 0 ...tch => 0035-Prevent-velocity-freeze.patch} | 0 ...n-option-to-disable-chat-signatures.patch} | 0 ...ch-invalid-entity-rotation-log-spam.patch} | 0 ...Patch-large-selector-distance-crash.patch} | 0 ...mit-sculk-catalyst-cursor-positions.patch} | 0 ...patch => 0040-Limit-map-decorations.patch} | 2 +- ...layer-banning-using-duplicate-UUIDs.patch} | 4 +- ...on-t-warn-on-duplicate-entity-UUIDs.patch} | 2 +- ...component-extra-empty-array-exploit.patch} | 0 ...pth-limit-to-Component-deserializer.patch} | 0 ...0045-Implement-command-block-events.patch} | 0 ...tch => 0046-Add-depth-limit-to-SNBT.patch} | 0 ...ch => 0047-Limit-beacon-effectRange.patch} | 0 ...ove-validation-of-ResourceLocations.patch} | 0 ...n-t-log-on-too-many-chained-updates.patch} | 0 ...050-Fix-packet-related-lag-exploits.patch} | 0 ...-Limit-save-data-for-Bees-and-Vexes.patch} | 0 ...tch => 0052-Mute-invalid-attributes.patch} | 0 ...053-Mute-invalid-Enderdragon-phases.patch} | 0 ...l-Components-in-the-Component-codec.patch} | 0 settings.gradle.kts | 2 +- submodules/AdvancedSlimePaper | 1 + 68 files changed, 2213 insertions(+), 19 deletions(-) create mode 100644 .gitmodules create mode 100644 aswm-api/build.gradle.kts create mode 120000 aswm-api/src create mode 100644 aswm-core/build.gradle.kts create mode 120000 aswm-core/src create mode 100644 patches/api/0001-AdvancedSlimePaper-API-Changes.patch rename patches/api/{0001-Add-MasterBlockFireEvent.patch => 0002-Add-MasterBlockFireEvent.patch} (94%) rename patches/api/{0002-Add-spectator-teleport-event.patch => 0003-Add-spectator-teleport-event.patch} (95%) rename patches/api/{0003-Add-Scissors-configuration-file.patch => 0004-Add-Scissors-configuration-file.patch} (100%) rename patches/api/{0004-Add-command-block-player-edit-event.patch => 0005-Add-command-block-player-edit-event.patch} (100%) create mode 100644 patches/server/0001-AdvancedSlimePaper-Server-Changes.patch rename patches/server/{0001-Build-changes.patch => 0002-Build-changes.patch} (92%) rename patches/server/{0002-UUID-validation.patch => 0003-UUID-validation.patch} (100%) rename patches/server/{0003-ResourceLocation-validation.patch => 0004-ResourceLocation-validation.patch} (99%) rename patches/server/{0004-Fixes-the-Blank-SkullOwner-exploit.patch => 0005-Fixes-the-Blank-SkullOwner-exploit.patch} (100%) rename patches/server/{0005-Fixes-log-spam-caused-by-invalid-entities-in-beehive.patch => 0006-Fixes-log-spam-caused-by-invalid-entities-in-beehive.patch} (100%) rename patches/server/{0006-Removes-useless-spammy-error-logging.patch => 0007-Removes-useless-spammy-error-logging.patch} (100%) rename patches/server/{0007-Ignore-errors-thrown-when-trying-to-remove-minecart-.patch => 0008-Ignore-errors-thrown-when-trying-to-remove-minecart-.patch} (100%) rename patches/server/{0008-ItemEntity-Check-if-items-are-air-before-calling-set.patch => 0009-ItemEntity-Check-if-items-are-air-before-calling-set.patch} (100%) rename patches/server/{0009-Fixes-Knowledge-Books-causing-log-spam-when-invalid-.patch => 0010-Fixes-Knowledge-Books-causing-log-spam-when-invalid-.patch} (100%) rename patches/server/{0010-Validate-BlockState-and-SoundEvent-values.patch => 0011-Validate-BlockState-and-SoundEvent-values.patch} (100%) rename patches/server/{0011-Do-not-log-invalid-items-in-HoverEvent-and-ItemFrame.patch => 0012-Do-not-log-invalid-items-in-HoverEvent-and-ItemFrame.patch} (100%) rename patches/server/{0012-Change-version-fetcher-to-AMG.patch => 0013-Change-version-fetcher-to-AMG.patch} (98%) rename patches/server/{0013-Better-handling-of-invalid-JSON-components.patch => 0014-Better-handling-of-invalid-JSON-components.patch} (100%) rename patches/server/{0014-Block-server-side-chunkbans.patch => 0015-Block-server-side-chunkbans.patch} (100%) rename patches/server/{0015-Reject-oversized-components-from-updating.patch => 0016-Reject-oversized-components-from-updating.patch} (100%) rename patches/server/{0016-Add-Scissors-configuration-file-command.patch => 0017-Add-Scissors-configuration-file-command.patch} (100%) rename patches/server/{0017-Prevent-attributes-with-invalid-namespaces-from-bein.patch => 0018-Prevent-attributes-with-invalid-namespaces-from-bein.patch} (100%) rename patches/server/{0018-Don-t-query-player-data-in-the-nbt-component.patch => 0019-Don-t-query-player-data-in-the-nbt-component.patch} (100%) rename patches/server/{0019-Limit-string-tag-visitors-to-1024-elements.patch => 0020-Limit-string-tag-visitors-to-1024-elements.patch} (100%) rename patches/server/{0020-Fixes-creative-killing-potion-effects-and-certain-po.patch => 0021-Fixes-creative-killing-potion-effects-and-certain-po.patch} (100%) rename patches/server/{0021-Fix-negative-death-times.patch => 0022-Fix-negative-death-times.patch} (100%) rename patches/server/{0022-Limit-amount-of-vehicle-collision-checks-to-3-and-di.patch => 0023-Limit-amount-of-vehicle-collision-checks-to-3-and-di.patch} (100%) rename patches/server/{0023-Add-custom-classes-used-by-Scissors.patch => 0024-Add-custom-classes-used-by-Scissors.patch} (100%) rename patches/server/{0024-Reset-large-tags.patch => 0025-Reset-large-tags.patch} (100%) rename patches/server/{0025-Don-t-log-invalid-teams-to-console.patch => 0026-Don-t-log-invalid-teams-to-console.patch} (100%) rename patches/server/{0026-Fixes-out-of-bounds-HangingEntity-crash-exploit.patch => 0027-Fixes-out-of-bounds-HangingEntity-crash-exploit.patch} (100%) rename patches/server/{0027-Add-MasterBlockFireEvent.patch => 0028-Add-MasterBlockFireEvent.patch} (100%) rename patches/server/{0028-Add-spectator-teleport-event.patch => 0029-Add-spectator-teleport-event.patch} (100%) rename patches/server/{0029-Prevent-invalid-container-events.patch => 0030-Prevent-invalid-container-events.patch} (100%) rename patches/server/{0030-Disable-running-commands-in-books-by-default.patch => 0031-Disable-running-commands-in-books-by-default.patch} (100%) rename patches/server/{0031-Validate-block-entity-entity-tag-query-positions.patch => 0032-Validate-block-entity-entity-tag-query-positions.patch} (100%) rename patches/server/{0032-Fix-ClickEvents-on-Signs-bypassing-permissions.patch => 0033-Fix-ClickEvents-on-Signs-bypassing-permissions.patch} (97%) rename patches/server/{0033-Refuse-to-convert-legacy-messages-over-1k-characters.patch => 0034-Refuse-to-convert-legacy-messages-over-1k-characters.patch} (100%) rename patches/server/{0034-Prevent-velocity-freeze.patch => 0035-Prevent-velocity-freeze.patch} (100%) rename patches/server/{0035-Add-configuration-option-to-disable-chat-signatures.patch => 0036-Add-configuration-option-to-disable-chat-signatures.patch} (100%) rename patches/server/{0036-Patch-invalid-entity-rotation-log-spam.patch => 0037-Patch-invalid-entity-rotation-log-spam.patch} (100%) rename patches/server/{0037-Patch-large-selector-distance-crash.patch => 0038-Patch-large-selector-distance-crash.patch} (100%) rename patches/server/{0038-Limit-sculk-catalyst-cursor-positions.patch => 0039-Limit-sculk-catalyst-cursor-positions.patch} (100%) rename patches/server/{0039-Limit-map-decorations.patch => 0040-Limit-map-decorations.patch} (95%) rename patches/server/{0040-Prevent-player-banning-using-duplicate-UUIDs.patch => 0041-Prevent-player-banning-using-duplicate-UUIDs.patch} (88%) rename patches/server/{0041-Don-t-warn-on-duplicate-entity-UUIDs.patch => 0042-Don-t-warn-on-duplicate-entity-UUIDs.patch} (91%) rename patches/server/{0042-Fix-component-extra-empty-array-exploit.patch => 0043-Fix-component-extra-empty-array-exploit.patch} (100%) rename patches/server/{0043-Add-depth-limit-to-Component-deserializer.patch => 0044-Add-depth-limit-to-Component-deserializer.patch} (100%) rename patches/server/{0044-Implement-command-block-events.patch => 0045-Implement-command-block-events.patch} (100%) rename patches/server/{0045-Add-depth-limit-to-SNBT.patch => 0046-Add-depth-limit-to-SNBT.patch} (100%) rename patches/server/{0046-Limit-beacon-effectRange.patch => 0047-Limit-beacon-effectRange.patch} (100%) rename patches/server/{0047-Improve-validation-of-ResourceLocations.patch => 0048-Improve-validation-of-ResourceLocations.patch} (100%) rename patches/server/{0048-Don-t-log-on-too-many-chained-updates.patch => 0049-Don-t-log-on-too-many-chained-updates.patch} (100%) rename patches/server/{0049-Fix-packet-related-lag-exploits.patch => 0050-Fix-packet-related-lag-exploits.patch} (100%) rename patches/server/{0050-Limit-save-data-for-Bees-and-Vexes.patch => 0051-Limit-save-data-for-Bees-and-Vexes.patch} (100%) rename patches/server/{0051-Mute-invalid-attributes.patch => 0052-Mute-invalid-attributes.patch} (100%) rename patches/server/{0052-Mute-invalid-Enderdragon-phases.patch => 0053-Mute-invalid-Enderdragon-phases.patch} (100%) rename patches/server/{0053-Don-t-return-null-Components-in-the-Component-codec.patch => 0054-Don-t-return-null-Components-in-the-Component-codec.patch} (100%) create mode 160000 submodules/AdvancedSlimePaper diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e9b11ec --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/AdvancedSlimePaper"] + path = submodules/AdvancedSlimePaper + url = git@github.com:InfernalSuite/AdvancedSlimePaper.git diff --git a/README.md b/README.md index c640311..adab081 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Scissors [![Build Status](https://ci.scissors.gg/job/Scissors/job/1.20.1/badge/icon)](https://ci.scissors.gg/job/Scissors/job/1.20.1/) Scissors is a fork of Paper that aims to fix exploits possible in Creative Mode. Many of these exploits are ones that -Paper's own team has either refused to fix or would have. +Paper's own team has either refused to fix or would have. + +All SWM patches/SWM API belongs to [AdvancedSlimePaper and InfernalSuite](https://github.com/InfernalSuite/AdvancedSlimePaper) ## Links ### [Scissors Download](https://ci.plex.us.org/job/Scissors) diff --git a/aswm-api/build.gradle.kts b/aswm-api/build.gradle.kts new file mode 100644 index 0000000..ba0eb6f --- /dev/null +++ b/aswm-api/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + `java-library` + `maven-publish` + signing +} + +dependencies { + api("com.flowpowered:flow-nbt:2.0.2") + api("org.jetbrains:annotations:23.0.0") + + compileOnly("io.papermc.paper:paper-api:1.20-R0.1-SNAPSHOT") +} + +java { + withSourcesJar() + withJavadocJar() +} diff --git a/aswm-api/src b/aswm-api/src new file mode 120000 index 0000000..7f4b0eb --- /dev/null +++ b/aswm-api/src @@ -0,0 +1 @@ +../submodules/AdvancedSlimePaper/api/src \ No newline at end of file diff --git a/aswm-core/build.gradle.kts b/aswm-core/build.gradle.kts new file mode 100644 index 0000000..40bd17b --- /dev/null +++ b/aswm-core/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { +} + +dependencies { + compileOnly(project(":aswm-api")) + implementation("com.github.luben:zstd-jni:1.5.2-2") +} \ No newline at end of file diff --git a/aswm-core/src b/aswm-core/src new file mode 120000 index 0000000..b08d836 --- /dev/null +++ b/aswm-core/src @@ -0,0 +1 @@ +../submodules/AdvancedSlimePaper/core/src \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index db662da..1545e9c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,6 +49,7 @@ subprojects { repositories { mavenCentral() maven(paperMavenPublicUrl) + maven("https://repo.rapture.pw/repository/maven-releases/") } } diff --git a/patches/api/0001-AdvancedSlimePaper-API-Changes.patch b/patches/api/0001-AdvancedSlimePaper-API-Changes.patch new file mode 100644 index 0000000..4163d76 --- /dev/null +++ b/patches/api/0001-AdvancedSlimePaper-API-Changes.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 26 Dec 2022 12:08:15 -0500 +Subject: [PATCH] AdvancedSlimePaper API Changes + +AdvancedSlimePaper +Copyright (C) 2023 InfernalSuite + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/build.gradle.kts b/build.gradle.kts +index 8045f92ffdfb4164bcbef99c41359590c45f9006..51e49260927f6840c3640275ff26e7398ec72b89 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -27,6 +27,7 @@ configurations.api { + + dependencies { + // api dependencies are listed transitively to API consumers ++ api(project(":aswm-api")) // ASWM + api("com.google.guava:guava:31.1-jre") + api("com.google.code.gson:gson:2.10") + api("net.md-5:bungeecord-chat:$bungeeCordChatVersion-deprecated+build.14") // Paper diff --git a/patches/api/0001-Add-MasterBlockFireEvent.patch b/patches/api/0002-Add-MasterBlockFireEvent.patch similarity index 94% rename from patches/api/0001-Add-MasterBlockFireEvent.patch rename to patches/api/0002-Add-MasterBlockFireEvent.patch index 12f29e3..92967a5 100644 --- a/patches/api/0001-Add-MasterBlockFireEvent.patch +++ b/patches/api/0002-Add-MasterBlockFireEvent.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Add MasterBlockFireEvent diff --git a/src/main/java/me/totalfreedom/scissors/event/block/MasterBlockFireEvent.java b/src/main/java/me/totalfreedom/scissors/event/block/MasterBlockFireEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..a24cb52a5af62012c5d5acc29e4c3558e92ae572 +index 0000000000000000000000000000000000000000..812e6ae9f1c8eb9558e5109c522d3ce3a7deb35c --- /dev/null +++ b/src/main/java/me/totalfreedom/scissors/event/block/MasterBlockFireEvent.java @@ -0,0 +1,51 @@ diff --git a/patches/api/0002-Add-spectator-teleport-event.patch b/patches/api/0003-Add-spectator-teleport-event.patch similarity index 95% rename from patches/api/0002-Add-spectator-teleport-event.patch rename to patches/api/0003-Add-spectator-teleport-event.patch index 4862b37..c5e85f0 100644 --- a/patches/api/0002-Add-spectator-teleport-event.patch +++ b/patches/api/0003-Add-spectator-teleport-event.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Add spectator teleport event diff --git a/src/main/java/me/totalfreedom/scissors/event/player/SpectatorTeleportEvent.java b/src/main/java/me/totalfreedom/scissors/event/player/SpectatorTeleportEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..d7efa63c316ed99c3eccfeadc1b0873b2ccb5d8a +index 0000000000000000000000000000000000000000..e4c9256c78f8b395aea86e9ea1a112f8e7426c1f --- /dev/null +++ b/src/main/java/me/totalfreedom/scissors/event/player/SpectatorTeleportEvent.java @@ -0,0 +1,60 @@ diff --git a/patches/api/0003-Add-Scissors-configuration-file.patch b/patches/api/0004-Add-Scissors-configuration-file.patch similarity index 100% rename from patches/api/0003-Add-Scissors-configuration-file.patch rename to patches/api/0004-Add-Scissors-configuration-file.patch diff --git a/patches/api/0004-Add-command-block-player-edit-event.patch b/patches/api/0005-Add-command-block-player-edit-event.patch similarity index 100% rename from patches/api/0004-Add-command-block-player-edit-event.patch rename to patches/api/0005-Add-command-block-player-edit-event.patch diff --git a/patches/server/0001-AdvancedSlimePaper-Server-Changes.patch b/patches/server/0001-AdvancedSlimePaper-Server-Changes.patch new file mode 100644 index 0000000..e63dae3 --- /dev/null +++ b/patches/server/0001-AdvancedSlimePaper-Server-Changes.patch @@ -0,0 +1,2128 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 26 Dec 2022 11:25:35 -0500 +Subject: [PATCH] AdvancedSlimePaper Server Changes + +AdvancedSlimePaper +Copyright (C) 2023 InfernalSuite + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/build.gradle.kts b/build.gradle.kts +index fb98936bb8a5488db75d676c5bcb4060597fbbf8..2143180a92ec6d0c0eba5559dd5497291348fdfa 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -13,6 +13,7 @@ configurations.named(log4jPlugins.compileClasspathConfigurationName) { + val alsoShade: Configuration by configurations.creating + + dependencies { ++ implementation(project(":aswm-core")) + implementation(project(":paper-api")) + implementation(project(":paper-mojangapi")) + // Paper start +diff --git a/src/main/java/com/infernalsuite/aswm/Converter.java b/src/main/java/com/infernalsuite/aswm/Converter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7d1753c0b7e89bbf0c245a0231b62773eca2779e +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/Converter.java +@@ -0,0 +1,118 @@ ++package com.infernalsuite.aswm; ++ ++import com.flowpowered.nbt.CompoundMap; ++import com.flowpowered.nbt.TagType; ++import com.infernalsuite.aswm.api.utils.NibbleArray; ++import net.minecraft.nbt.*; ++import net.minecraft.world.level.chunk.DataLayer; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class Converter { ++ ++ private static final Logger LOGGER = LogManager.getLogger("SWM Converter"); ++ ++ static DataLayer convertArray(NibbleArray array) { ++ return new DataLayer(array.getBacking()); ++ } ++ ++ public static NibbleArray convertArray(DataLayer array) { ++ if(array == null) { ++ return null; ++ } ++ ++ return new NibbleArray(array.getData()); ++ } ++ ++ public static Tag convertTag(com.flowpowered.nbt.Tag tag) { ++ try { ++ switch(tag.getType()) { ++ case TAG_BYTE: ++ return ByteTag.valueOf(((com.flowpowered.nbt.ByteTag) tag).getValue()); ++ case TAG_SHORT: ++ return ShortTag.valueOf(((com.flowpowered.nbt.ShortTag) tag).getValue()); ++ case TAG_INT: ++ return IntTag.valueOf(((com.flowpowered.nbt.IntTag) tag).getValue()); ++ case TAG_LONG: ++ return LongTag.valueOf(((com.flowpowered.nbt.LongTag) tag).getValue()); ++ case TAG_FLOAT: ++ return FloatTag.valueOf(((com.flowpowered.nbt.FloatTag) tag).getValue()); ++ case TAG_DOUBLE: ++ return DoubleTag.valueOf(((com.flowpowered.nbt.DoubleTag) tag).getValue()); ++ case TAG_BYTE_ARRAY: ++ return new ByteArrayTag(((com.flowpowered.nbt.ByteArrayTag) tag).getValue()); ++ case TAG_STRING: ++ return StringTag.valueOf(((com.flowpowered.nbt.StringTag) tag).getValue()); ++ case TAG_LIST: ++ ListTag list = new ListTag(); ++ ((com.flowpowered.nbt.ListTag) tag).getValue().stream().map(Converter::convertTag).forEach(list::add); ++ ++ return list; ++ case TAG_COMPOUND: ++ CompoundTag compound = new CompoundTag(); ++ ++ ((com.flowpowered.nbt.CompoundTag) tag).getValue().forEach((key, value) -> compound.put(key, convertTag(value))); ++ return compound; ++ case TAG_INT_ARRAY: ++ return new IntArrayTag(((com.flowpowered.nbt.IntArrayTag) tag).getValue()); ++ case TAG_LONG_ARRAY: ++ return new LongArrayTag(((com.flowpowered.nbt.LongArrayTag) tag).getValue()); ++ default: ++ throw new IllegalArgumentException("Invalid tag type " + tag.getType().name()); ++ } ++ } catch(Exception ex) { ++ LOGGER.error("Failed to convert NBT object:"); ++ LOGGER.error(tag.toString()); ++ ++ throw ex; ++ } ++ } ++ ++ public static com.flowpowered.nbt.Tag convertTag(String name, Tag base) { ++ switch(base.getId()) { ++ case Tag.TAG_BYTE: ++ return new com.flowpowered.nbt.ByteTag(name, ((ByteTag) base).getAsByte()); ++ case Tag.TAG_SHORT: ++ return new com.flowpowered.nbt.ShortTag(name, ((ShortTag) base).getAsShort()); ++ case Tag.TAG_INT: ++ return new com.flowpowered.nbt.IntTag(name, ((IntTag) base).getAsInt()); ++ case Tag.TAG_LONG: ++ return new com.flowpowered.nbt.LongTag(name, ((LongTag) base).getAsLong()); ++ case Tag.TAG_FLOAT: ++ return new com.flowpowered.nbt.FloatTag(name, ((FloatTag) base).getAsFloat()); ++ case Tag.TAG_DOUBLE: ++ return new com.flowpowered.nbt.DoubleTag(name, ((DoubleTag) base).getAsDouble()); ++ case Tag.TAG_BYTE_ARRAY: ++ return new com.flowpowered.nbt.ByteArrayTag(name, ((ByteArrayTag) base).getAsByteArray()); ++ case Tag.TAG_STRING: ++ return new com.flowpowered.nbt.StringTag(name, ((StringTag) base).getAsString()); ++ case Tag.TAG_LIST: ++ List list = new ArrayList<>(); ++ ListTag originalList = ((ListTag) base); ++ ++ for(Tag entry : originalList) { ++ list.add(convertTag("", entry)); ++ } ++ ++ return new com.flowpowered.nbt.ListTag(name, TagType.getById(originalList.getElementType()), list); ++ case Tag.TAG_COMPOUND: ++ CompoundTag originalCompound = ((CompoundTag) base); ++ com.flowpowered.nbt.CompoundTag compound = new com.flowpowered.nbt.CompoundTag(name, new CompoundMap()); ++ ++ for(String key : originalCompound.getAllKeys()) { ++ compound.getValue().put(key, convertTag(key, originalCompound.get(key))); ++ } ++ ++ return compound; ++ case Tag.TAG_INT_ARRAY: ++ return new com.flowpowered.nbt.IntArrayTag(name, ((IntArrayTag) base).getAsIntArray()); ++ case Tag.TAG_LONG_ARRAY: ++ return new com.flowpowered.nbt.LongArrayTag(name, ((LongArrayTag) base).getAsLongArray()); ++ default: ++ throw new IllegalArgumentException("Invalid tag type " + base.getId()); ++ } ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/InternalPlugin.java b/src/main/java/com/infernalsuite/aswm/InternalPlugin.java +new file mode 100644 +index 0000000000000000000000000000000000000000..61518ab2b68e7a41500f3c8c8a5ec1230597f0e5 +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/InternalPlugin.java +@@ -0,0 +1,28 @@ ++package com.infernalsuite.aswm; ++ ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Server; ++import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; ++import org.bukkit.plugin.PluginLogger; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.logging.LogRecord; ++ ++public class InternalPlugin extends MinecraftInternalPlugin { ++ ++ @Override ++ public @NotNull Server getServer() { ++ return MinecraftServer.getServer().server; ++ } ++ ++ @Override ++ public @NotNull PluginLogger getLogger() { ++ return new PluginLogger(new InternalPlugin()) { ++ @Override ++ public void log(@NotNull LogRecord logRecord) { ++ MinecraftServer.LOGGER.info(logRecord.getMessage()); ++ } ++ }; ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1affe4c94b490a05184deccc9eb80530f67fd5ea +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java +@@ -0,0 +1,101 @@ ++package com.infernalsuite.aswm; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import com.flowpowered.nbt.CompoundTag; ++import com.infernalsuite.aswm.serialization.SlimeWorldReader; ++import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld; ++import com.infernalsuite.aswm.skeleton.SlimeChunkSectionSkeleton; ++import com.infernalsuite.aswm.skeleton.SlimeChunkSkeleton; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.infernalsuite.aswm.api.world.SlimeChunkSection; ++import com.infernalsuite.aswm.api.world.SlimeWorld; ++import net.minecraft.SharedConstants; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.function.Consumer; ++ ++class SimpleDataFixerConverter implements SlimeWorldReader { ++ ++ @Override ++ public SlimeWorld readFromData(SlimeWorld data) { ++ int newVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ int currentVersion = data.getDataVersion(); ++ // Already fixed ++ if (currentVersion == newVersion) { ++ return data; ++ } ++ ++ Map chunks = new HashMap<>(); ++ for (SlimeChunk chunk : data.getChunkStorage()) { ++ List entities = new ArrayList<>(); ++ List blockEntities = new ArrayList<>(); ++ for (CompoundTag upgradeEntity : chunk.getTileEntities()) { ++ blockEntities.add( ++ convertAndBack(upgradeEntity, (tag) -> MCTypeRegistry.TILE_ENTITY.convert(new NBTMapType(tag), currentVersion, newVersion)) ++ ); ++ } ++ for (CompoundTag upgradeEntity : chunk.getEntities()) { ++ entities.add( ++ convertAndBack(upgradeEntity, (tag) -> MCTypeRegistry.ENTITY.convert(new NBTMapType(tag), currentVersion, newVersion)) ++ ); ++ } ++ ++ SlimeChunkSection[] sections = new SlimeChunkSection[chunk.getSections().length]; ++ for (int i = 0; i < sections.length; i++) { ++ SlimeChunkSection dataSection = chunk.getSections()[i]; ++ ++ com.flowpowered.nbt.CompoundTag blockStateTag = blockStateTag = convertAndBack(dataSection.getBlockStatesTag(), (tag) -> { ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, new NBTMapType(tag), "palette", currentVersion, newVersion); ++ }); ++ ++ com.flowpowered.nbt.CompoundTag biomeTag = convertAndBack(dataSection.getBiomeTag(), (tag) -> { ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, new NBTMapType(tag), "palette", currentVersion, newVersion); ++ }); ++ ++ sections[i] = new SlimeChunkSectionSkeleton( ++ blockStateTag, ++ biomeTag, ++ dataSection.getBlockLight(), ++ dataSection.getSkyLight() ++ ); ++ ++ chunks.put(new ChunkPos(chunk.getX(), chunk.getZ()), new SlimeChunkSkeleton( ++ chunk.getX(), ++ chunk.getZ(), ++ sections, ++ chunk.getHeightMaps(), ++ blockEntities, ++ entities ++ )); ++ } ++ ++ } ++ ++ return new SkeletonSlimeWorld( ++ data.getName(), ++ data.getLoader(), ++ data.isReadOnly(), ++ chunks, ++ data.getExtraData(), ++ data.getPropertyMap(), ++ newVersion ++ ); ++ } ++ ++ ++ private static com.flowpowered.nbt.CompoundTag convertAndBack(com.flowpowered.nbt.CompoundTag value, Consumer acceptor) { ++ if (value == null) { ++ return null; ++ } ++ ++ net.minecraft.nbt.CompoundTag converted = (net.minecraft.nbt.CompoundTag) Converter.convertTag(value); ++ acceptor.accept(converted); ++ ++ return (com.flowpowered.nbt.CompoundTag) Converter.convertTag(value.getName(), converted); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java b/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..20f16757ba8b8da525ff17d51d4f7eb660c4d22b +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java +@@ -0,0 +1,206 @@ ++package com.infernalsuite.aswm; ++ ++import com.infernalsuite.aswm.api.SlimeNMSBridge; ++import com.infernalsuite.aswm.api.world.SlimeWorld; ++import com.infernalsuite.aswm.api.world.SlimeWorldInstance; ++import com.infernalsuite.aswm.api.world.properties.SlimeProperties; ++import com.infernalsuite.aswm.level.SlimeBootstrap; ++import com.infernalsuite.aswm.level.SlimeInMemoryWorld; ++import com.infernalsuite.aswm.level.SlimeLevelInstance; ++import com.mojang.serialization.Lifecycle; ++import net.kyori.adventure.util.Services; ++import net.minecraft.SharedConstants; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.dedicated.DedicatedServer; ++import net.minecraft.server.dedicated.DedicatedServerProperties; ++import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelSettings; ++import net.minecraft.world.level.dimension.LevelStem; ++import net.minecraft.world.level.levelgen.WorldOptions; ++import net.minecraft.world.level.storage.CommandStorage; ++import net.minecraft.world.level.storage.DimensionDataStorage; ++import net.minecraft.world.level.storage.PrimaryLevelData; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.IOException; ++import java.util.Locale; ++ ++public class SlimeNMSBridgeImpl implements SlimeNMSBridge { ++ ++ private static final SimpleDataFixerConverter DATA_FIXER_CONVERTER = new SimpleDataFixerConverter(); ++ ++ private static final Logger LOGGER = LogManager.getLogger("SWM"); ++ ++ private SlimeWorld defaultWorld; ++ private SlimeWorld defaultNetherWorld; ++ private SlimeWorld defaultEndWorld; ++ ++ public static SlimeNMSBridgeImpl instance() { ++ return (SlimeNMSBridgeImpl) SlimeNMSBridge.instance(); ++ } ++ ++ @Override ++ public boolean loadOverworldOverride() { ++ if (defaultWorld == null) { ++ return false; ++ } ++ ++ // See MinecraftServer loading logic ++ // Some stuff is needed when loading overworld world ++ SlimeLevelInstance instance = ((SlimeInMemoryWorld) this.loadInstance(defaultWorld, Level.OVERWORLD)).getInstance(); ++ DimensionDataStorage worldpersistentdata = instance.getDataStorage(); ++ instance.getCraftServer().scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(instance.getServer(), instance.getScoreboard()); ++ instance.getServer().commandStorage = new CommandStorage(worldpersistentdata); ++ ++ return true; ++ } ++ ++ @Override ++ public boolean loadNetherOverride() { ++ if (defaultNetherWorld == null) { ++ return false; ++ } ++ ++ this.loadInstance(defaultNetherWorld, Level.NETHER); ++ ++ return true; ++ } ++ ++ @Override ++ public boolean loadEndOverride() { ++ if (defaultEndWorld == null) { ++ return false; ++ } ++ ++ this.loadInstance(defaultEndWorld, Level.END); ++ ++ return true; ++ } ++ ++ @Override ++ public void setDefaultWorlds(SlimeWorld normalWorld, SlimeWorld netherWorld, SlimeWorld endWorld) { ++ if (normalWorld != null) { ++ normalWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.NORMAL.toString().toLowerCase()); ++ defaultWorld = normalWorld; ++ } ++ ++ if (netherWorld != null) { ++ netherWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.NETHER.toString().toLowerCase()); ++ defaultNetherWorld = netherWorld; ++ } ++ ++ if (endWorld != null) { ++ endWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.THE_END.toString().toLowerCase()); ++ defaultEndWorld = endWorld; ++ } ++ ++ } ++ ++ @Override ++ public SlimeWorldInstance loadInstance(SlimeWorld slimeWorld) { ++ return this.loadInstance(slimeWorld, null); ++ } ++ ++ public SlimeWorldInstance loadInstance(SlimeWorld slimeWorld, @Nullable ResourceKey dimensionOverride) { ++ String worldName = slimeWorld.getName(); ++ ++ if (Bukkit.getWorld(worldName) != null) { ++ throw new IllegalArgumentException("World " + worldName + " already exists! Maybe it's an outdated SlimeWorld object?"); ++ } ++ ++ SlimeLevelInstance server = createCustomWorld(slimeWorld, dimensionOverride); ++ registerWorld(server); ++ return server.getSlimeInstance(); ++ } ++ ++ @Override ++ public SlimeWorldInstance getInstance(World world) { ++ CraftWorld craftWorld = (CraftWorld) world; ++ ++ if (!(craftWorld.getHandle() instanceof SlimeLevelInstance worldServer)) { ++ return null; ++ } ++ ++ return worldServer.getSlimeInstance(); ++ } ++ ++ @Override ++ public SlimeWorld applyDataFixers(SlimeWorld world) { ++ return DATA_FIXER_CONVERTER.readFromData(world); ++ } ++ ++ ++ @Override ++ public int getCurrentVersion() { ++ return SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ } ++ ++ public void registerWorld(SlimeLevelInstance server) { ++ MinecraftServer mcServer = MinecraftServer.getServer(); ++ mcServer.initWorld(server, server.serverLevelData, mcServer.getWorldData(), server.serverLevelData.worldGenOptions()); ++ ++ mcServer.addLevel(server); ++ } ++ ++ private SlimeLevelInstance createCustomWorld(SlimeWorld world, @Nullable ResourceKey dimensionOverride) { ++ SlimeBootstrap bootstrap = new SlimeBootstrap(world); ++ String worldName = world.getName(); ++ ++ PrimaryLevelData worldDataServer = createWorldData(world); ++ World.Environment environment = getEnvironment(world); ++ ResourceKey dimension = switch (environment) { ++ case NORMAL -> LevelStem.OVERWORLD; ++ case NETHER -> LevelStem.NETHER; ++ case THE_END -> LevelStem.END; ++ default -> throw new IllegalArgumentException("Unknown dimension supplied"); ++ }; ++ ++ ResourceKey worldKey = dimensionOverride == null ? ResourceKey.create(Registries.DIMENSION, new ResourceLocation(worldName.toLowerCase(Locale.ENGLISH))) : dimensionOverride; ++ LevelStem stem = MinecraftServer.getServer().registries().compositeAccess().registryOrThrow(Registries.LEVEL_STEM).get(dimension); ++ ++ SlimeLevelInstance level; ++ ++ try { ++ level = new SlimeLevelInstance(bootstrap, worldDataServer, worldKey, dimension, stem, environment); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); // TODO do something better with this? ++ } ++ ++ // level.setReady(true); ++ level.setSpawnSettings(world.getPropertyMap().getValue(SlimeProperties.ALLOW_MONSTERS), world.getPropertyMap().getValue(SlimeProperties.ALLOW_ANIMALS)); ++ ++ return level; ++ } ++ ++ private World.Environment getEnvironment(SlimeWorld world) { ++ return World.Environment.valueOf(world.getPropertyMap().getValue(SlimeProperties.ENVIRONMENT).toUpperCase()); ++ } ++ ++ private PrimaryLevelData createWorldData(SlimeWorld world) { ++ MinecraftServer mcServer = MinecraftServer.getServer(); ++ DedicatedServerProperties serverProps = ((DedicatedServer) mcServer).getProperties(); ++ String worldName = world.getName(); ++ ++ LevelSettings worldsettings = new LevelSettings(worldName, serverProps.gamemode, false, serverProps.difficulty, ++ true, new GameRules(), mcServer.worldLoader.dataConfiguration()); ++ ++ WorldOptions worldoptions = new WorldOptions(0, false, false); ++ ++ PrimaryLevelData data = new PrimaryLevelData(worldsettings, worldoptions, PrimaryLevelData.SpecialWorldProperty.FLAT, Lifecycle.stable()); ++ data.checkName(worldName); ++ data.setModdedInfo(mcServer.getServerModName(), mcServer.getModdedStatus().shouldReportAsModified()); ++ data.setInitialized(true); ++ ++ return data; ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41e652b568598926e838e81fdc338e51f8e97ef8 +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java +@@ -0,0 +1,121 @@ ++package com.infernalsuite.aswm.level; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.poi.PoiChunk; ++import io.papermc.paper.chunk.system.scheduling.ChunkLoadTask; ++import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler; ++import io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ImposterProtoChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.UpgradeData; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.ticks.LevelChunkTicks; ++import org.slf4j.Logger; ++ ++import java.util.function.Consumer; ++ ++public final class ChunkDataLoadTask implements CommonLoadTask { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ private final ChunkTaskScheduler scheduler; ++ private final ServerLevel world; ++ private final int chunkX; ++ private final int chunkZ; ++ private Consumer> onRun; ++ ++ private PrioritisedExecutor.PrioritisedTask task; ++ ++ private final ChunkLoadTask chunkLoadTask; ++ ++ protected ChunkDataLoadTask(ChunkLoadTask chunkLoadTask, final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, ++ final int chunkZ, final PrioritisedExecutor.Priority priority, final Consumer> onRun) { ++ this.chunkLoadTask = chunkLoadTask; ++ this.scheduler = scheduler; ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.onRun = onRun; ++ ++ this.task = this.scheduler.createChunkTask(this.chunkX, this.chunkZ, () -> { ++ try { ++ SlimeChunk chunk = ((SlimeLevelInstance) this.world).slimeInstance.getChunk(this.chunkX, this.chunkZ); ++ this.onRun.accept(new GenericDataLoadTask.TaskResult<>(runOnMain(chunk), null)); ++ } catch (Throwable e) { ++ LOGGER.error("ERROR", e); ++ this.onRun.accept(new GenericDataLoadTask.TaskResult<>(null, e)); ++ } ++ }, priority); ++ } ++ ++ private ChunkAccess getEmptyChunk() { ++ LevelChunkTicks blockLevelChunkTicks = new LevelChunkTicks<>(); ++ LevelChunkTicks fluidLevelChunkTicks = new LevelChunkTicks<>(); ++ ++ return new ImposterProtoChunk(new LevelChunk(this.world, new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, ++ 0L, null, null, null), true); ++ } ++ ++ protected ChunkAccess runOnMain(final SlimeChunk data) { ++ final PoiChunk poiChunk = this.chunkLoadTask.chunkHolder.getPoiChunk(); ++ if (poiChunk == null) { ++ LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString()); ++ } else { ++ poiChunk.load(); ++ } ++ ++ // have tasks to run (at this point, it's just the POI consistency checking) ++ try { ++ // if (data.tasks != null) { ++ // for (int i = 0, len = data.tasks.size(); i < len; i) { ++ // data.tasks.poll().run(); ++ // } ++ // } ++ ++ LevelChunk chunk = this.world.slimeInstance.promote(chunkX, chunkZ, data); ++ ++ return new ImposterProtoChunk(chunk, false); ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr2) { ++ LOGGER.error("Failed to parse main tasks for task " + this.toString() + ", chunk data will be lost", thr2); ++ return this.getEmptyChunk(); ++ } ++ } ++ ++ @Override ++ public PrioritisedExecutor.Priority getPriority() { ++ return this.task.getPriority(); ++ } ++ ++ @Override ++ public void setPriority(PrioritisedExecutor.Priority priority) { ++ this.task.setPriority(priority); ++ } ++ ++ @Override ++ public void raisePriority(PrioritisedExecutor.Priority priority) { ++ this.task.raisePriority(priority); ++ } ++ ++ @Override ++ public void lowerPriority(PrioritisedExecutor.Priority priority) { ++ this.task.lowerPriority(priority); ++ } ++ ++ @Override ++ public boolean cancel() { ++ return this.task.cancel(); ++ } ++ ++ public boolean schedule(boolean schedule) { ++ this.scheduler.scheduleChunkTask(chunkX, chunkZ, this.task::execute); ++ return true; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fc6e46972bcc77134ed718c8c157ec3893d4bcdf +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java +@@ -0,0 +1,18 @@ ++package com.infernalsuite.aswm.level; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++ ++public interface CommonLoadTask { ++ ++ boolean schedule(boolean schedule); ++ ++ PrioritisedExecutor.Priority getPriority(); ++ ++ boolean cancel(); ++ ++ void lowerPriority(PrioritisedExecutor.Priority priority); ++ ++ void raisePriority(PrioritisedExecutor.Priority priority); ++ ++ void setPriority(PrioritisedExecutor.Priority priority); ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java b/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0e47f25e9be33da374dc737c96d8d3c2bb1cd0f +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java +@@ -0,0 +1,59 @@ ++package com.infernalsuite.aswm.level; ++ ++import com.infernalsuite.aswm.api.world.SlimeWorld; ++import com.infernalsuite.aswm.api.world.properties.SlimeProperties; ++import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; ++import io.papermc.paper.world.ChunkEntitySlices; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++ ++public class FastChunkPruner { ++ ++ public static boolean canBePruned(SlimeWorld world, LevelChunk chunk) { ++ // Kenox ++ // It's not safe to assume that the chunk can be pruned ++ // if there isn't a loaded chunk there ++ if (chunk == null || chunk.getChunkHolder() == null) { ++ return false; ++ } ++ ++ SlimePropertyMap propertyMap = world.getPropertyMap(); ++ if (propertyMap.getValue(SlimeProperties.SHOULD_LIMIT_SAVE)) { ++ int minX = propertyMap.getValue(SlimeProperties.SAVE_MIN_X); ++ int maxX = propertyMap.getValue(SlimeProperties.SAVE_MAX_X); ++ ++ int minZ = propertyMap.getValue(SlimeProperties.SAVE_MIN_Z); ++ int maxZ = propertyMap.getValue(SlimeProperties.SAVE_MAX_Z); ++ ++ int chunkX = chunk.locX; ++ int chunkZ = chunk.locZ; ++ ++ if (chunkX < minX || chunkX > maxX) { ++ return true; ++ } ++ ++ if (chunkZ < minZ || chunkZ > maxZ) { ++ return true; ++ } ++ } ++ ++ String pruningSetting = world.getPropertyMap().getValue(SlimeProperties.CHUNK_PRUNING); ++ if (pruningSetting.equals("aggressive")) { ++ ChunkEntitySlices slices = chunk.getChunkHolder().getEntityChunk(); ++ ++ return chunk.blockEntities.isEmpty() && (slices == null || slices.isEmpty()) && areSectionsEmpty(chunk); ++ } ++ ++ return false; ++ } ++ ++ private static boolean areSectionsEmpty(LevelChunk chunk) { ++ for (LevelChunkSection section : chunk.getSections()) { ++ if (!section.hasOnlyAir()) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f1db2fe121bb3aabfad727a8133b645524b8f19a +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java +@@ -0,0 +1,203 @@ ++package com.infernalsuite.aswm.level; ++ ++import com.flowpowered.nbt.CompoundMap; ++import com.flowpowered.nbt.CompoundTag; ++import com.flowpowered.nbt.LongArrayTag; ++import com.google.common.collect.Lists; ++import com.infernalsuite.aswm.Converter; ++import com.infernalsuite.aswm.skeleton.SlimeChunkSectionSkeleton; ++import com.infernalsuite.aswm.api.utils.NibbleArray; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.infernalsuite.aswm.api.world.SlimeChunkSection; ++import com.mojang.logging.LogUtils; ++import com.mojang.serialization.Codec; ++import io.papermc.paper.world.ChunkEntitySlices; ++import net.minecraft.core.Holder; ++import net.minecraft.core.Registry; ++import net.minecraft.core.SectionPos; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.LightLayer; ++import net.minecraft.world.level.biome.Biome; ++import net.minecraft.world.level.biome.Biomes; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.PalettedContainer; ++import net.minecraft.world.level.chunk.PalettedContainerRO; ++import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import net.minecraft.world.level.levelgen.Heightmap; ++import net.minecraft.world.level.lighting.LevelLightEngine; ++import org.slf4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++ ++public class NMSSlimeChunk implements SlimeChunk { ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ private static final CompoundTag EMPTY_BLOCK_STATE_PALETTE; ++ private static final CompoundTag EMPTY_BIOME_PALETTE; ++ ++ // Optimized empty section serialization ++ static { ++ { ++ PalettedContainer empty = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); ++ Tag tag = ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, empty).getOrThrow(false, (error) -> { ++ throw new AssertionError(error); ++ }); ++ ++ EMPTY_BLOCK_STATE_PALETTE = (CompoundTag) Converter.convertTag("", tag); ++ } ++ { ++ Registry biomes = net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME); ++ PalettedContainer> empty = new PalettedContainer<>(biomes.asHolderIdMap(), biomes.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); ++ Tag tag = ChunkSerializer.makeBiomeCodec(biomes).encodeStart(NbtOps.INSTANCE, empty).getOrThrow(false, (error) -> { ++ throw new AssertionError(error); ++ }); ++ ++ EMPTY_BIOME_PALETTE = (CompoundTag) Converter.convertTag("", tag); ++ } ++ } ++ ++ private LevelChunk chunk; ++ ++ public NMSSlimeChunk(LevelChunk chunk) { ++ this.chunk = chunk; ++ } ++ ++ @Override ++ public int getX() { ++ return chunk.getPos().x; ++ } ++ ++ @Override ++ public int getZ() { ++ return chunk.getPos().z; ++ } ++ ++ @Override ++ public SlimeChunkSection[] getSections() { ++ SlimeChunkSection[] sections = new SlimeChunkSection[this.chunk.getSectionsCount()]; ++ LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine(); ++ ++ Registry biomeRegistry = chunk.getLevel().registryAccess().registryOrThrow(Registries.BIOME); ++ ++ // Ignore deprecation, spigot only method ++ Codec>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS)); ++ ++ for (int sectionId = 0; sectionId < chunk.getSections().length; sectionId++) { ++ LevelChunkSection section = chunk.getSections()[sectionId]; ++ // Sections CANNOT be null in 1.18 ++ ++ // Block Light Nibble Array ++ NibbleArray blockLightArray = Converter.convertArray(lightEngine.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunk.getPos(), sectionId))); ++ ++ // Sky light Nibble Array ++ NibbleArray skyLightArray = Converter.convertArray(lightEngine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunk.getPos(), sectionId))); ++ ++ // Tile/Entity Data ++ ++ // Block Data ++ CompoundTag blockStateTag; ++ if (section.hasOnlyAir()) { ++ blockStateTag = EMPTY_BLOCK_STATE_PALETTE; ++ } else { ++ Tag data = ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, System.err::println); // todo error handling ++ blockStateTag = (CompoundTag) Converter.convertTag("", data); ++ } ++ ++ ++ CompoundTag biomeTag; ++ PalettedContainer> biomes = (PalettedContainer>) section.getBiomes(); ++ if (biomes.data.palette().getSize() == 1 && biomes.data.palette().maybeHas((h) -> h.is(Biomes.PLAINS))) { ++ biomeTag = EMPTY_BIOME_PALETTE; ++ } else { ++ Tag biomeData = codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, System.err::println); // todo error handling ++ biomeTag = (CompoundTag) Converter.convertTag("", biomeData); ++ } ++ ++ sections[sectionId] = new SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); ++ } ++ ++ return sections; ++ } ++ ++ @Override ++ public CompoundTag getHeightMaps() { ++ // HeightMap ++ CompoundMap heightMaps = new CompoundMap(); ++ ++ for (Map.Entry entry : chunk.heightmaps.entrySet()) { ++ if (!entry.getKey().keepAfterWorldgen()) { ++ continue; ++ } ++ ++ Heightmap.Types type = entry.getKey(); ++ Heightmap map = entry.getValue(); ++ ++ heightMaps.put(type.name(), new LongArrayTag(type.name(), map.getRawData())); ++ } ++ ++ return new CompoundTag("", heightMaps); ++ } ++ ++ @Override ++ public List getTileEntities() { ++ List tileEntities = new ArrayList<>(); ++ ++ for (BlockEntity entity : chunk.blockEntities.values()) { ++ net.minecraft.nbt.CompoundTag entityNbt = entity.saveWithFullMetadata(); ++ tileEntities.add(entityNbt); ++ } ++ ++ return Lists.transform(tileEntities, (compound) -> { ++ return (CompoundTag) Converter.convertTag("", compound); ++ }); ++ } ++ ++ @Override ++ public List getEntities() { ++ List entities = new ArrayList<>(); ++ ++ if(this.chunk == null || this.chunk.getChunkHolder() == null) { ++ return new ArrayList<>(); ++ } ++ ++ ChunkEntitySlices slices = this.chunk.getChunkHolder().getEntityChunk(); ++ if (slices == null) { ++ return new ArrayList<>(); ++ } ++ ++ // Work by ++ for (Entity entity : slices.entities) { ++ net.minecraft.nbt.CompoundTag entityNbt = new net.minecraft.nbt.CompoundTag(); ++ try { ++ if (entity.save(entityNbt)) { ++ entities.add(entityNbt); ++ } ++ } catch (Exception e) { ++ LOGGER.error("Could not save the entity = {}, exception = {}", entity, e); ++ } ++ } ++ ++ return Lists.transform(entities, (compound) -> { ++ return (CompoundTag) Converter.convertTag("", compound); ++ }); ++ } ++ ++ public LevelChunk getChunk() { ++ return chunk; ++ } ++ ++ public void setChunk(LevelChunk chunk) { ++ this.chunk = chunk; ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a27369c00345bbb94aa19f77687269dc94c0b0a +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java +@@ -0,0 +1,91 @@ ++package com.infernalsuite.aswm.level; ++ ++import com.flowpowered.nbt.CompoundTag; ++import com.infernalsuite.aswm.api.exceptions.WorldAlreadyExistsException; ++import com.infernalsuite.aswm.api.loaders.SlimeLoader; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.infernalsuite.aswm.api.world.SlimeWorld; ++import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; ++import net.minecraft.SharedConstants; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++import java.io.IOException; ++import java.util.Collection; ++import java.util.List; ++import java.util.Objects; ++import java.util.stream.Collectors; ++ ++public class NMSSlimeWorld implements SlimeWorld { ++ ++ private final SlimeInMemoryWorld memoryWorld; ++ private final SlimeLevelInstance instance; ++ ++ public NMSSlimeWorld(SlimeInMemoryWorld memoryWorld) { ++ this.instance = memoryWorld.getInstance(); ++ this.memoryWorld = memoryWorld; ++ } ++ ++ @Override ++ public String getName() { ++ return this.instance.getMinecraftWorld().serverLevelData.getLevelName(); ++ } ++ ++ @Override ++ public SlimeLoader getLoader() { ++ return this.instance.slimeInstance.getSaveStrategy(); ++ } ++ ++ @Override ++ public SlimeChunk getChunk(int x, int z) { ++ LevelChunk chunk = this.instance.getChunkIfLoaded(x, z); ++ if (chunk == null) { ++ return null; ++ } ++ ++ return new NMSSlimeChunk(chunk); ++ } ++ ++ @Override ++ public Collection getChunkStorage() { ++ List chunks = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.instance); // Paper ++ return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull) ++ .map(NMSSlimeChunk::new) ++ .collect(Collectors.toList()); ++ } ++ ++ @Override ++ public CompoundTag getExtraData() { ++ return this.instance.slimeInstance.getExtraData(); ++ } ++ ++ @Override ++ public Collection getWorldMaps() { ++ return List.of(); ++ } ++ ++ @Override ++ public SlimePropertyMap getPropertyMap() { ++ return this.instance.slimeInstance.getPropertyMap(); ++ } ++ ++ @Override ++ public boolean isReadOnly() { ++ return this.getLoader() == null; ++ } ++ ++ @Override ++ public SlimeWorld clone(String worldName) { ++ return this.memoryWorld.clone(worldName); ++ } ++ ++ @Override ++ public SlimeWorld clone(String worldName, SlimeLoader loader) throws WorldAlreadyExistsException, IOException { ++ return this.memoryWorld.clone(worldName, loader); ++ } ++ ++ @Override ++ public int getDataVersion() { ++ return SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b20a037679182e3c4a8bf31f084078f6d7e4ff46 +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java +@@ -0,0 +1,83 @@ ++package com.infernalsuite.aswm.level; ++ ++import com.flowpowered.nbt.CompoundTag; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.infernalsuite.aswm.api.world.SlimeChunkSection; ++ ++import java.util.List; ++ ++public class SafeNmsChunkWrapper implements SlimeChunk { ++ ++ private final NMSSlimeChunk wrapper; ++ private final SlimeChunk safety; ++ ++ public SafeNmsChunkWrapper(NMSSlimeChunk wrapper, SlimeChunk safety) { ++ this.wrapper = wrapper; ++ this.safety = safety; ++ } ++ ++ @Override ++ public int getX() { ++ return this.wrapper.getX(); ++ } ++ ++ @Override ++ public int getZ() { ++ return this.wrapper.getZ(); ++ } ++ ++ @Override ++ public SlimeChunkSection[] getSections() { ++ if (shouldDefaultBackToSlimeChunk()) { ++ return this.safety.getSections(); ++ } ++ ++ return this.wrapper.getSections(); ++ } ++ ++ @Override ++ public CompoundTag getHeightMaps() { ++ if (shouldDefaultBackToSlimeChunk()) { ++ return this.safety.getHeightMaps(); ++ } ++ ++ return this.wrapper.getHeightMaps(); ++ } ++ ++ @Override ++ public List getTileEntities() { ++ if (shouldDefaultBackToSlimeChunk()) { ++ return this.safety.getTileEntities(); ++ } ++ ++ return this.wrapper.getTileEntities(); ++ } ++ ++ @Override ++ public List getEntities() { ++ if (shouldDefaultBackToSlimeChunk()) { ++ return this.safety.getEntities(); ++ } ++ ++ return this.wrapper.getEntities(); ++ } ++ ++ /* ++Slime chunks can still be requested but not actually loaded, this caused ++some things to not properly save because they are not "loaded" into the chunk. ++See ChunkMap#protoChunkToFullChunk ++anything in the if statement will not be loaded and is stuck inside the runnable. ++Inorder to possibly not corrupt the state, simply refer back to the slime saved object. ++*/ ++ public boolean shouldDefaultBackToSlimeChunk() { ++ return this.safety != null && !this.wrapper.getChunk().loaded; ++ } ++ ++ public NMSSlimeChunk getWrapper() { ++ return wrapper; ++ } ++ ++ public SlimeChunk getSafety() { ++ return safety; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java b/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8853088c5c6306511716bbffac9bf73c633b61bb +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java +@@ -0,0 +1,8 @@ ++package com.infernalsuite.aswm.level; ++ ++import com.infernalsuite.aswm.api.world.SlimeWorld; ++ ++public record SlimeBootstrap( ++ SlimeWorld initial ++) { ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..91a7f41db47c7df3ecc301e0827a1d07305f604e +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java +@@ -0,0 +1,164 @@ ++package com.infernalsuite.aswm.level; ++ ++import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; ++import com.flowpowered.nbt.CompoundMap; ++import com.flowpowered.nbt.CompoundTag; ++import com.flowpowered.nbt.LongArrayTag; ++import com.infernalsuite.aswm.Converter; ++import com.infernalsuite.aswm.api.utils.NibbleArray; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.infernalsuite.aswm.api.world.SlimeChunkSection; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Holder; ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.biome.Biome; ++import net.minecraft.world.level.biome.Biomes; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.PalettedContainer; ++import net.minecraft.world.level.chunk.UpgradeData; ++import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import net.minecraft.world.level.levelgen.Heightmap; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.ticks.LevelChunkTicks; ++ ++import java.util.EnumSet; ++import java.util.List; ++import java.util.Optional; ++ ++public class SlimeChunkConverter { ++ ++ static SlimeChunkLevel deserializeSlimeChunk(SlimeLevelInstance instance, SlimeChunk chunk) { ++ int x = chunk.getX(); ++ int z = chunk.getZ(); ++ ++ ChunkPos pos = new ChunkPos(x, z); ++ ++ // Chunk sections ++ LevelChunkSection[] sections = new LevelChunkSection[instance.getSectionsCount()]; ++ ++ SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(instance); ++ SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(instance); ++ instance.getServer().scheduleOnMain(() -> { ++ instance.getLightEngine().retainData(pos, true); ++ }); ++ ++ Registry biomeRegistry = instance.registryAccess().registryOrThrow(Registries.BIOME); ++ // Ignore deprecated method ++ ++ Codec>> codec = PalettedContainer.codecRW(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS), null); ++ ++ for (int sectionId = 0; sectionId < chunk.getSections().length; sectionId++) { ++ SlimeChunkSection slimeSection = chunk.getSections()[sectionId]; ++ ++ if (slimeSection != null) { ++ NibbleArray blockLight = slimeSection.getBlockLight(); ++ if (blockLight != null) { ++ blockNibbles[sectionId] = new SWMRNibbleArray(blockLight.getBacking()); ++ } ++ ++ NibbleArray skyLight = slimeSection.getSkyLight(); ++ if (skyLight != null) { ++ skyNibbles[sectionId] = new SWMRNibbleArray(skyLight.getBacking()); ++ } ++ ++ PalettedContainer blockPalette; ++ if (slimeSection.getBlockStatesTag() != null) { ++ DataResult> dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, Converter.convertTag(slimeSection.getBlockStatesTag())).promotePartial((s) -> { ++ System.out.println("Recoverable error when parsing section " + x + "," + z + ": " + s); // todo proper logging ++ }); ++ blockPalette = dataresult.getOrThrow(false, System.err::println); // todo proper logging ++ } else { ++ blockPalette = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); ++ } ++ ++ PalettedContainer> biomePalette; ++ ++ if (slimeSection.getBiomeTag() != null) { ++ DataResult>> dataresult = codec.parse(NbtOps.INSTANCE, Converter.convertTag(slimeSection.getBiomeTag())).promotePartial((s) -> { ++ System.out.println("Recoverable error when parsing section " + x + "," + z + ": " + s); // todo proper logging ++ }); ++ biomePalette = dataresult.getOrThrow(false, System.err::println); // todo proper logging ++ } else { ++ biomePalette = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); ++ } ++ ++ if (sectionId < sections.length) { ++ LevelChunkSection section = new LevelChunkSection(blockPalette, biomePalette); ++ sections[sectionId] = section; ++ } ++ } ++ } ++ ++ // Keep the chunk loaded at level 33 to avoid light glitches ++ // Such a high level will let the server not tick the chunk, ++ // but at the same time it won't be completely unloaded from memory ++ // getChunkProvider().addTicket(SWM_TICKET, pos, 33, Unit.INSTANCE); ++ ++ ++ LevelChunk.PostLoadProcessor loadEntities = (nmsChunk) -> { ++ ++ // TODO ++ // Load tile entities ++ List tileEntities = chunk.getTileEntities(); ++ ++ if (tileEntities != null) { ++ for (CompoundTag tag : tileEntities) { ++ Optional type = tag.getStringValue("id"); ++ ++ // Sometimes null tile entities are saved ++ if (type.isPresent()) { ++ BlockPos blockPosition = new BlockPos(tag.getIntValue("x").get(), tag.getIntValue("y").get(), tag.getIntValue("z").get()); ++ BlockState blockData = nmsChunk.getBlockState(blockPosition); ++ BlockEntity entity = BlockEntity.loadStatic(blockPosition, blockData, (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag)); ++ ++ if (entity != null) { ++ nmsChunk.setBlockEntity(entity); ++ } ++ } ++ } ++ } ++ }; ++ ++ LevelChunkTicks blockLevelChunkTicks = new LevelChunkTicks<>(); ++ LevelChunkTicks fluidLevelChunkTicks = new LevelChunkTicks<>(); ++ SlimeChunkLevel nmsChunk = new SlimeChunkLevel(instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, 0L, sections, loadEntities, null); ++ ++ // Height Maps ++ EnumSet heightMapTypes = nmsChunk.getStatus().heightmapsAfter(); ++ CompoundMap heightMaps = chunk.getHeightMaps().getValue(); ++ EnumSet unsetHeightMaps = EnumSet.noneOf(Heightmap.Types.class); ++ ++ // Light ++ nmsChunk.setBlockNibbles(blockNibbles); ++ nmsChunk.setSkyNibbles(skyNibbles); ++ ++ for (Heightmap.Types type : heightMapTypes) { ++ String name = type.getSerializedName(); ++ ++ if (heightMaps.containsKey(name)) { ++ LongArrayTag heightMap = (LongArrayTag) heightMaps.get(name); ++ nmsChunk.setHeightmap(type, heightMap.getValue()); ++ } else { ++ unsetHeightMaps.add(type); ++ } ++ } ++ ++ // Don't try to populate heightmaps if there are none. ++ // Does a crazy amount of block lookups ++ if (!unsetHeightMaps.isEmpty()) { ++ Heightmap.primeHeightmaps(nmsChunk, unsetHeightMaps); ++ } ++ ++ return nmsChunk; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b159fc8751e9840b311cc1eda01e496e2dbc5f2e +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java +@@ -0,0 +1,27 @@ ++package com.infernalsuite.aswm.level; ++ ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.UpgradeData; ++import net.minecraft.world.level.levelgen.blending.BlendingData; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.ticks.LevelChunkTicks; ++import org.jetbrains.annotations.Nullable; ++ ++public class SlimeChunkLevel extends LevelChunk { ++ ++ private final SlimeInMemoryWorld inMemoryWorld; ++ ++ public SlimeChunkLevel(SlimeLevelInstance world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { ++ super(world, pos, upgradeData, blockTickScheduler, fluidTickScheduler, inhabitedTime, sectionArrayInitializer, entityLoader, blendingData); ++ this.inMemoryWorld = world.slimeInstance; ++ } ++ ++ @Override ++ public void unloadCallback() { ++ super.unloadCallback(); ++ this.inMemoryWorld.unload(this); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fd4cfb9cceb4f23265cb3cce7f1f251051bfba92 +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java +@@ -0,0 +1,251 @@ ++package com.infernalsuite.aswm.level; ++ ++import com.flowpowered.nbt.CompoundTag; ++import com.infernalsuite.aswm.ChunkPos; ++import com.infernalsuite.aswm.api.exceptions.WorldAlreadyExistsException; ++import com.infernalsuite.aswm.api.loaders.SlimeLoader; ++import com.infernalsuite.aswm.serialization.slime.SlimeSerializer; ++import com.infernalsuite.aswm.skeleton.SkeletonCloning; ++import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld; ++import com.infernalsuite.aswm.skeleton.SlimeChunkSkeleton; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.infernalsuite.aswm.api.world.SlimeWorld; ++import com.infernalsuite.aswm.api.world.SlimeWorldInstance; ++import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.UpgradeData; ++import net.minecraft.world.level.material.Fluid; ++import net.minecraft.world.ticks.LevelChunkTicks; ++import org.bukkit.World; ++ ++import java.io.IOException; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++/* ++The concept of this is a bit flawed, since ideally this should be a 1:1 representation of the MC world. ++However, due to the complexity of the chunk system we essentially need to wrap around it. ++This stores slime chunks, and when unloaded, will properly convert it to a normal slime chunk for storage. ++ */ ++public class SlimeInMemoryWorld implements SlimeWorld, SlimeWorldInstance { ++ ++ private final SlimeLevelInstance instance; ++ private final SlimeWorld liveWorld; ++ ++ private final CompoundTag extra; ++ private final SlimePropertyMap propertyMap; ++ private final SlimeLoader loader; ++ ++ private final Map chunkStorage = new HashMap<>(); ++ private boolean readOnly; ++ // private final Map> entityStorage = new HashMap<>(); ++ ++ public SlimeInMemoryWorld(SlimeBootstrap bootstrap, SlimeLevelInstance instance) { ++ this.instance = instance; ++ this.extra = bootstrap.initial().getExtraData(); ++ this.propertyMap = bootstrap.initial().getPropertyMap(); ++ this.loader = bootstrap.initial().getLoader(); ++ this.readOnly = bootstrap.initial().isReadOnly(); ++ ++ for (SlimeChunk initial : bootstrap.initial().getChunkStorage()) { ++ ChunkPos pos = new ChunkPos(initial.getX(), initial.getZ()); ++ List tags = new ArrayList<>(initial.getEntities()); ++ ++ // this.entityStorage.put(pos, tags); ++ this.chunkStorage.put(pos, initial); ++ } ++ ++ this.liveWorld = new NMSSlimeWorld(this); ++ } ++ ++ @Override ++ public String getName() { ++ return this.instance.getMinecraftWorld().serverLevelData.getLevelName(); ++ } ++ ++ @Override ++ public SlimeLoader getLoader() { ++ return this.loader; ++ } ++ ++ public LevelChunk promote(int x, int z, SlimeChunk chunk) { ++ SlimeChunkLevel levelChunk; ++ if (chunk == null) { ++ net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(x, z); ++ LevelChunkTicks blockLevelChunkTicks = new LevelChunkTicks<>(); ++ LevelChunkTicks fluidLevelChunkTicks = new LevelChunkTicks<>(); ++ ++ levelChunk = new SlimeChunkLevel(this.instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, ++ 0L, null, null, null); ++ ++ chunk = new NMSSlimeChunk(levelChunk); ++ ++ } else { ++ levelChunk = SlimeChunkConverter.deserializeSlimeChunk(this.instance, chunk); ++ chunk = new SafeNmsChunkWrapper(new NMSSlimeChunk(levelChunk), chunk); ++ } ++ this.chunkStorage.put(new ChunkPos(x, z), chunk); ++ ++ return levelChunk; ++ } ++ ++ // Authored by: Kenox ++ // Don't use the NMS live chunk in the chunk map ++ public void unload(LevelChunk providedChunk) { ++ final int x = providedChunk.locX; ++ final int z = providedChunk.locZ; ++ ++ SlimeChunk chunk = new NMSSlimeChunk(providedChunk); ++ ++ if (FastChunkPruner.canBePruned(this.liveWorld, providedChunk)) { ++ this.chunkStorage.remove(new ChunkPos(x, z)); ++ return; ++ } ++ ++ this.chunkStorage.put(new ChunkPos(x, z), ++ new SlimeChunkSkeleton(chunk.getX(), chunk.getZ(), chunk.getSections(), ++ chunk.getHeightMaps(), chunk.getTileEntities(), chunk.getEntities())); ++ } ++ ++ @Override ++ public SlimeChunk getChunk(int x, int z) { ++ return this.chunkStorage.get(new ChunkPos(x, z)); ++ } ++ ++ @Override ++ public Collection getChunkStorage() { ++ return this.chunkStorage.values(); ++ } ++ ++ @Override ++ public World getBukkitWorld() { ++ return this.instance.getWorld(); ++ } ++ ++ @Override ++ public SlimeWorld getSlimeWorldMirror() { ++ return this.liveWorld; ++ } ++ ++ @Override ++ public SlimePropertyMap getPropertyMap() { ++ return this.propertyMap; ++ } ++ ++ @Override ++ public boolean isReadOnly() { ++ return this.getSaveStrategy() == null || this.readOnly; ++ } ++ ++ @Override ++ public SlimeWorld clone(String worldName) { ++ try { ++ return clone(worldName, null); ++ } catch (WorldAlreadyExistsException | IOException ignored) { ++ return null; // Never going to happen ++ } ++ } ++ ++ @Override ++ public SlimeWorld clone(String worldName, SlimeLoader loader) throws WorldAlreadyExistsException, IOException { ++ if (this.getName().equals(worldName)) { ++ throw new IllegalArgumentException("The clone world cannot have the same name as the original world!"); ++ } ++ ++ if (worldName == null) { ++ throw new IllegalArgumentException("The world name cannot be null!"); ++ } ++ if (loader != null) { ++ if (loader.worldExists(worldName)) { ++ throw new WorldAlreadyExistsException(worldName); ++ } ++ } ++ ++ SlimeWorld cloned = SkeletonCloning.fullClone(worldName, this, loader); ++ if (loader != null) { ++ loader.saveWorld(worldName, SlimeSerializer.serialize(cloned)); ++ } ++ ++ return cloned; ++ } ++ ++ @Override ++ public int getDataVersion() { ++ return this.liveWorld.getDataVersion(); ++ } ++ ++ @Override ++ public SlimeLoader getSaveStrategy() { ++ return this.loader; ++ } ++ ++ @Override ++ public CompoundTag getExtraData() { ++ return this.extra; ++ } ++ ++ @Override ++ public Collection getWorldMaps() { ++ return List.of(); ++ } ++ ++ // public Map> getEntityStorage() { ++ // return entityStorage; ++ // } ++ ++ public SlimeWorld getForSerialization() { ++ SlimeWorld world = SkeletonCloning.weakCopy(this); ++ ++ Map cloned = new HashMap<>(); ++ for (Map.Entry entry : this.chunkStorage.entrySet()) { ++ SlimeChunk clonedChunk = entry.getValue(); ++ // NMS "live" chunks need to be converted ++ { ++ LevelChunk chunk = null; ++ if (clonedChunk instanceof SafeNmsChunkWrapper safeNmsChunkWrapper) { ++ if (safeNmsChunkWrapper.shouldDefaultBackToSlimeChunk()) { ++ clonedChunk = safeNmsChunkWrapper.getSafety(); ++ } else { ++ chunk = safeNmsChunkWrapper.getWrapper().getChunk(); ++ } ++ } else if (clonedChunk instanceof NMSSlimeChunk nmsSlimeChunk) { ++ chunk = nmsSlimeChunk.getChunk(); ++ } ++ ++ if (chunk != null) { ++ if (FastChunkPruner.canBePruned(world, chunk)) { ++ continue; ++ } ++ ++ clonedChunk = new SlimeChunkSkeleton( ++ clonedChunk.getX(), ++ clonedChunk.getZ(), ++ clonedChunk.getSections(), ++ clonedChunk.getHeightMaps(), ++ clonedChunk.getTileEntities(), ++ clonedChunk.getEntities() ++ ); ++ } ++ } ++ ++ cloned.put(entry.getKey(), clonedChunk); ++ } ++ ++ return new SkeletonSlimeWorld(world.getName(), ++ world.getLoader(), ++ world.isReadOnly(), ++ cloned, ++ world.getExtraData(), ++ world.getPropertyMap(), ++ world.getDataVersion() ++ ); ++ } ++ ++ public SlimeLevelInstance getInstance() { ++ return instance; ++ } ++} +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4f48b7a1a41aabc78cc9276fbf9f372cb117003f +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java +@@ -0,0 +1,39 @@ ++package com.infernalsuite.aswm.level; ++ ++import com.mojang.serialization.Codec; ++import net.minecraft.core.Holder; ++import net.minecraft.world.level.biome.Biome; ++import net.minecraft.world.level.biome.BiomeSource; ++import net.minecraft.world.level.biome.Climate; ++import net.minecraft.world.level.levelgen.FlatLevelSource; ++import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; ++ ++import java.util.List; ++import java.util.Optional; ++import java.util.stream.Stream; ++ ++public class SlimeLevelGenerator extends FlatLevelSource { ++ ++ public SlimeLevelGenerator(Holder biome) { ++ super(new FlatLevelGeneratorSettings(Optional.empty(), biome, List.of()), getSource(biome)); ++ } ++ ++ private static BiomeSource getSource(Holder biome) { ++ return new BiomeSource() { ++ @Override ++ protected Codec codec() { ++ return null; ++ } ++ ++ @Override ++ protected Stream> collectPossibleBiomes() { ++ return Stream.of(biome); ++ } ++ ++ @Override ++ public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler noise) { ++ return biome; ++ } ++ }; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2d18d76829b6dc590913d974d50dbaafcb79e175 +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java +@@ -0,0 +1,195 @@ ++package com.infernalsuite.aswm.level; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import com.infernalsuite.aswm.Converter; ++import com.infernalsuite.aswm.serialization.slime.SlimeSerializer; ++import com.infernalsuite.aswm.api.world.SlimeChunk; ++import com.infernalsuite.aswm.api.world.SlimeWorld; ++import com.infernalsuite.aswm.api.world.SlimeWorldInstance; ++import com.infernalsuite.aswm.api.world.properties.SlimeProperties; ++import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; ++import io.papermc.paper.chunk.system.scheduling.ChunkLoadTask; ++import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler; ++import io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Holder; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.util.ProgressListener; ++import net.minecraft.util.Unit; ++import net.minecraft.util.datafix.DataFixers; ++import net.minecraft.world.Difficulty; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.biome.Biome; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.dimension.LevelStem; ++import net.minecraft.world.level.storage.LevelStorageSource; ++import net.minecraft.world.level.storage.PrimaryLevelData; ++import net.minecraft.world.level.validation.DirectoryValidator; ++import net.minecraft.world.level.validation.PathAllowList; ++import org.apache.commons.io.FileUtils; ++import org.bukkit.Bukkit; ++import org.bukkit.event.world.WorldSaveEvent; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.IOException; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.UUID; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import java.util.concurrent.Future; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++import java.util.stream.Collectors; ++ ++public class SlimeLevelInstance extends ServerLevel { ++ ++ ++ public static LevelStorageSource CUSTOM_LEVEL_STORAGE; ++ ++ static { ++ try { ++ Path path = Files.createTempDirectory("swm-" + UUID.randomUUID().toString().substring(0, 5)).toAbsolutePath(); ++ DirectoryValidator directoryvalidator = LevelStorageSource.parseValidator(path.resolve("allowed_symlinks.txt")); ++ CUSTOM_LEVEL_STORAGE = new LevelStorageSource(path, path, directoryvalidator, DataFixers.getDataFixer()); ++ ++ FileUtils.forceDeleteOnExit(path.toFile()); ++ ++ } catch (IOException ex) { ++ throw new IllegalStateException("Couldn't create dummy file directory.", ex); ++ } ++ } ++ ++ private static final ExecutorService WORLD_SAVER_SERVICE = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder() ++ .setNameFormat("SWM Pool Thread #%1$d").build()); ++ private static final TicketType SWM_TICKET = TicketType.create("swm-chunk", (a, b) -> 0); ++ ++ private final Object saveLock = new Object(); ++ ++ private boolean ready = false; ++ ++ public SlimeLevelInstance(SlimeBootstrap slimeBootstrap, PrimaryLevelData primaryLevelData, ++ ResourceKey worldKey, ++ ResourceKey dimensionKey, LevelStem worldDimension, ++ org.bukkit.World.Environment environment) throws IOException { ++ ++ super(slimeBootstrap, MinecraftServer.getServer(), MinecraftServer.getServer().executor, ++ CUSTOM_LEVEL_STORAGE.createAccess(slimeBootstrap.initial().getName() + UUID.randomUUID(), dimensionKey), ++ primaryLevelData, worldKey, worldDimension, ++ MinecraftServer.getServer().progressListenerFactory.create(11), false, 0, ++ Collections.emptyList(), true, environment, null, null); ++ this.slimeInstance = new SlimeInMemoryWorld(slimeBootstrap, this); ++ ++ ++ SlimePropertyMap propertyMap = slimeBootstrap.initial().getPropertyMap(); ++ ++ this.serverLevelData.setDifficulty(Difficulty.valueOf(propertyMap.getValue(SlimeProperties.DIFFICULTY).toUpperCase())); ++ this.serverLevelData.setSpawn(new BlockPos(propertyMap.getValue(SlimeProperties.SPAWN_X), propertyMap.getValue(SlimeProperties.SPAWN_Y), propertyMap.getValue(SlimeProperties.SPAWN_Z)), 0); ++ super.setSpawnSettings(propertyMap.getValue(SlimeProperties.ALLOW_MONSTERS), propertyMap.getValue(SlimeProperties.ALLOW_ANIMALS)); ++ ++ this.pvpMode = propertyMap.getValue(SlimeProperties.PVP); ++ ++ this.keepSpawnInMemory = false; ++ } ++ ++ @Override ++ public ChunkGenerator getGenerator(SlimeBootstrap slimeBootstrap) { ++ String biomeStr = slimeBootstrap.initial().getPropertyMap().getValue(SlimeProperties.DEFAULT_BIOME); ++ ResourceKey biomeKey = ResourceKey.create(Registries.BIOME, new ResourceLocation(biomeStr)); ++ Holder defaultBiome = MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolder(biomeKey).orElseThrow(); ++ return new SlimeLevelGenerator(defaultBiome); ++ } ++ ++ @Override ++ public void save(@Nullable ProgressListener progressUpdate, boolean forceSave, boolean savingDisabled, boolean close) { ++ try { ++ if (!this.slimeInstance.isReadOnly() && !savingDisabled) { ++ Bukkit.getPluginManager().callEvent(new WorldSaveEvent(getWorld())); ++ ++ //this.getChunkSource().save(forceSave); ++ this.serverLevelData.setWorldBorder(this.getWorldBorder().createSettings()); ++ this.serverLevelData.setCustomBossEvents(MinecraftServer.getServer().getCustomBossEvents().save()); ++ ++ // Update level data ++ net.minecraft.nbt.CompoundTag compound = new net.minecraft.nbt.CompoundTag(); ++ net.minecraft.nbt.CompoundTag nbtTagCompound = this.serverLevelData.createTag(MinecraftServer.getServer().registryAccess(), compound); ++ ++ if (MinecraftServer.getServer().isStopped()) { // Make sure the world gets saved before stopping the server by running it from the main thread ++ save().get(); // Async wait for it to finish ++ this.slimeInstance.getLoader().unlockWorld(this.slimeInstance.getName()); // Unlock ++ } else { ++ this.save(); ++ //WORLD_SAVER_SERVICE.execute(this::save); ++ } ++ } ++ } catch (Throwable e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ @Override ++ public void saveIncrementally(boolean doFull) { ++ if (doFull) { ++ this.save(null, false, false); ++ } ++ } ++ ++ private Future save() { ++ synchronized (saveLock) { // Don't want to save the SlimeWorld from multiple threads simultaneously ++ SlimeWorldInstance slimeWorld = this.slimeInstance; ++ Bukkit.getLogger().log(Level.INFO, "Saving world " + this.slimeInstance.getName() + "..."); ++ long start = System.currentTimeMillis(); ++ ++ Bukkit.getLogger().log(Level.INFO, "CONVERTING NMS -> SKELETON"); ++ SlimeWorld world = this.slimeInstance.getForSerialization(); ++ Bukkit.getLogger().log(Level.INFO, "CONVERTED TO SKELETON, PUSHING OFF-THREAD"); ++ return WORLD_SAVER_SERVICE.submit(() -> { ++ try { ++ byte[] serializedWorld = SlimeSerializer.serialize(world); ++ long saveStart = System.currentTimeMillis(); ++ slimeWorld.getSaveStrategy().saveWorld(slimeWorld.getName(), serializedWorld); ++ Bukkit.getLogger().log(Level.INFO, "World " + slimeWorld.getName() + " serialized in " + (saveStart - start) + "ms and saved in " + (System.currentTimeMillis() - saveStart) + "ms."); ++ } catch (IOException | IllegalStateException ex) { ++ ex.printStackTrace(); ++ } ++ }); ++ ++ } ++ } ++ ++ public SlimeWorldInstance getSlimeInstance() { ++ return this.slimeInstance; ++ } ++ ++ public ChunkDataLoadTask getLoadTask(ChunkLoadTask task, ChunkTaskScheduler scheduler, ServerLevel world, int chunkX, int chunkZ, PrioritisedExecutor.Priority priority, Consumer> onRun) { ++ return new ChunkDataLoadTask(task, scheduler, world, chunkX, chunkZ, priority, onRun); ++ } ++ ++ public void loadEntities(int chunkX, int chunkZ) { ++ SlimeChunk slimeChunk = this.slimeInstance.getChunk(chunkX, chunkZ); ++ if (slimeChunk != null) { ++ this.getEntityLookup().addLegacyChunkEntities(new ArrayList<>( ++ EntityType.loadEntitiesRecursive(slimeChunk.getEntities() ++ .stream() ++ .map((tag) -> (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag)) ++ .collect(Collectors.toList()), this) ++ .toList() ++ )); ++ } ++ } ++ ++ // @Override ++ // public void unload(LevelChunk chunk) { ++ // this.slimeInstance.unload(chunk); ++ // super.unload(chunk); ++ // } ++} +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java b/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3500005bb09dc484bc333f1e0799613d097a37d3 +--- /dev/null ++++ b/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java +@@ -0,0 +1,9 @@ ++package com.infernalsuite.aswm.util; ++ ++public class NmsUtil { ++ ++ public static long asLong(int chunkX, int chunkZ) { ++ return (((long) chunkZ) * Integer.MAX_VALUE + ((long) chunkX)); ++ //return (long)chunkX & 4294967295L | ((long)chunkZ & 4294967295L) << 32; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +index abd0217cf0bff183c8e262edc173a53403797c1a..ab450a0ffbfd914c33323d740b3c382a96cb0e8f 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +@@ -165,7 +165,8 @@ public final class ChunkHolderManager { + return this.chunkHolders.size(); + } + +- public void close(final boolean save, final boolean halt) { ++ public void close(boolean save, final boolean halt) { // ASWM ++ if (this.world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance) save = false; // ASWM + TickThread.ensureTickThread("Closing world off-main"); + if (halt) { + LOGGER.info("Waiting 60s for chunk system to halt for world '" + this.world.getWorld().getName() + "'"); +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java +index e7fb084ddb88ab62f1d493a999cc82b9258d275e..7e5b9c4033e81d5c348ff9eb110b8c9cd1b3aeeb 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java +@@ -6,6 +6,7 @@ import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; + import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; + import ca.spottedleaf.dataconverter.minecraft.MCDataConverter; + import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.infernalsuite.aswm.level.CommonLoadTask; // ASWM + import com.mojang.logging.LogUtils; + import io.papermc.paper.chunk.system.io.RegionFileIOThread; + import io.papermc.paper.chunk.system.poi.PoiChunk; +@@ -32,8 +33,8 @@ public final class ChunkLoadTask extends ChunkProgressionTask { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + +- private final NewChunkHolder chunkHolder; +- private final ChunkDataLoadTask loadTask; ++ public final NewChunkHolder chunkHolder; // ASWM - public -> private ++ private final CommonLoadTask loadTask; // ASWM + + private volatile boolean cancelled; + private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; +@@ -45,11 +46,20 @@ public final class ChunkLoadTask extends ChunkProgressionTask { + final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { + super(scheduler, world, chunkX, chunkZ); + this.chunkHolder = chunkHolder; +- this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); +- this.loadTask.addCallback((final GenericDataLoadTask.TaskResult result) -> { +- ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement +- ChunkLoadTask.this.tryCompleteLoad(); +- }); ++ // ASWM start ++ if (world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance levelInstance) { ++ ++ this.loadTask = levelInstance.getLoadTask(this, scheduler, world, chunkX, chunkZ, priority, result -> { ++ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); ++ }); ++ } else { ++ ChunkDataLoadTask task = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); ++ task.addCallback((final GenericDataLoadTask.TaskResult result) -> { ++ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); ++ }); ++ this.loadTask = task; ++ } ++ // ASWM end + } + + private void tryCompleteLoad() { +@@ -274,7 +284,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { + } + } + +- public static final class ChunkDataLoadTask extends CallbackDataLoadTask { ++ public static final class ChunkDataLoadTask extends CallbackDataLoadTask implements CommonLoadTask { // ASWM + protected ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, + final int chunkZ, final PrioritisedExecutor.Priority priority) { + super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +index 51304c5cf4b0ac7646693ef97ef4a3847d3342b5..56280c18be01deabc53932c1580dce7b3d83e06f 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +@@ -112,7 +112,10 @@ public final class NewChunkHolder { + } + + if (!transientChunk) { +- if (entityChunk != null) { ++ // ASWM start ++ if (this.world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance world) { ++ world.loadEntities(this.chunkX, this.chunkZ); ++ } else if (entityChunk != null) { // ASWM end + final List entities = EntityStorage.readEntities(this.world, entityChunk); + + this.world.getEntityLookup().addEntityChunkEntities(entities); +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +index 7e8dc9e8f381abfdcce2746edc93122d623622d1..148db7995f8bb8bef8f1eeeadc338dffab891d57 100644 +--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -36,7 +36,7 @@ public final class ChunkEntitySlices { + protected final EntityCollectionBySection allEntities; + protected final EntityCollectionBySection hardCollidingEntities; + protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; +- protected final EntityList entities = new EntityList(); ++ public final EntityList entities = new EntityList(); // ASWM - protected -> public + + public FullChunkStatus status; + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 435f5ee3388f5da93df938c43ea2578f7d586407..9d636e893885a411cde5fb72f9a79da0d2f7cbf9 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -272,7 +272,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public + private final CustomBossEvents customBossEvents; + private final ServerFunctionManager functionManager; + private final FrameTimer frameTimer; +@@ -462,18 +462,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); + // Paper end + +- public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { ++ // ASWM start ++ public final com.infernalsuite.aswm.level.SlimeBootstrap bootstrap; ++ public ServerChunkCache(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap, ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { // ASWM ++ this.bootstrap = bootstrap; ++ // ASWM end + this.level = world; + this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world); + this.mainThread = Thread.currentThread(); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 18aac3da3c88f33b1a71a5920a8daa27e9723913..20099425babf5325005d11bf25faa783e3bc2715 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -643,6 +643,14 @@ public class ServerLevel extends Level implements WorldGenLevel { + + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { ++ // ASWM start ++ this(null, minecraftserver, executor, convertable_conversionsession, iworlddataserver, resourcekey, worlddimension, worldloadlistener, flag, i, list, flag1, env, gen, biomeProvider); ++ } ++ ++ public com.infernalsuite.aswm.level.SlimeInMemoryWorld slimeInstance; ++ ++ public ServerLevel(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap, MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { ++ // ASWM end + // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error + // Holder holder = worlddimension.type(); // CraftBukkit - decompile error + +@@ -681,6 +689,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen); + } + // CraftBukkit end ++ // ASWM start ++ ChunkGenerator result = this.getGenerator(bootstrap); ++ if (result != null) chunkgenerator = result; ++ // ASWM end + boolean flag2 = minecraftserver.forceSynchronousWrites(); + DataFixer datafixer = minecraftserver.getFixerUpper(); + this.entityStorage = new EntityRegionFileStorage(convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); +@@ -692,7 +704,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system + + //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system +- this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system ++ this.chunkSource = new ServerChunkCache(bootstrap, this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // ASWM // Paper - rewrite chunk system + return minecraftserver.overworld().getDataStorage(); + }); + this.chunkSource.getGeneratorState().ensureStructuresGenerated(); +@@ -721,7 +733,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + this.sleepStatus = new SleepStatus(); + this.gameEventDispatcher = new GameEventDispatcher(this); +- this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> { ++ this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(getRandomSequences(), () -> { // ASWM + return (RandomSequences) this.getDataStorage().computeIfAbsent((nbttagcompound) -> { + return RandomSequences.load(l, nbttagcompound); + }, () -> { +@@ -748,6 +760,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.dragonFight = enderDragonFight; + } + ++ // ASWM start ++ public ChunkGenerator getGenerator(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap) { ++ return null; ++ } ++ // ASWM end ++ + public void setWeatherParameters(int clearDuration, int rainDuration, boolean raining, boolean thundering) { + this.serverLevelData.setClearWeatherTime(clearDuration); + this.serverLevelData.setRainTime(rainDuration); +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 7f5547dc31aa53b2863f4c09f598fa88e7fe2afd..7b28346e8256013d071b265d9e382738bb4b33c8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -30,7 +30,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + }; + public final IdMap registry; + private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values +- private volatile PalettedContainer.Data data; ++ public volatile PalettedContainer.Data data; // ASWM - private -> public + private final PalettedContainer.Strategy strategy; + // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused + +@@ -398,7 +398,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + void accept(T object, int count); + } + +- static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) { ++ public static record Data(PalettedContainer.Configuration configuration, BitStorage storage, Palette palette) { // ASWM - private -> public + public void copyFrom(Palette palette, BitStorage storage) { + for(int i = 0; i < storage.getSize(); ++i) { + T object = palette.valueFor(storage.get(i)); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 9c6a2884c34a9f6e775103da42480cd6b8c693b3..8f70e99dc48bd8b0e6ebe7a955b8e3e1a826aabf 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -434,7 +434,7 @@ public class ChunkSerializer { + ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message); + } + +- private static Codec>> makeBiomeCodec(Registry biomeRegistry) { ++ public static Codec>> makeBiomeCodec(Registry biomeRegistry) { // ASWM - private -> public + return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS)); + } + +diff --git a/src/main/resources/META-INF/services/com.infernalsuite.aswm.api.SlimeNMSBridge b/src/main/resources/META-INF/services/com.infernalsuite.aswm.api.SlimeNMSBridge +new file mode 100644 +index 0000000000000000000000000000000000000000..916b4d2edba2f2a8a0fc1fdb6ab6a57e2a16f938 +--- /dev/null ++++ b/src/main/resources/META-INF/services/com.infernalsuite.aswm.api.SlimeNMSBridge +@@ -0,0 +1 @@ ++com.infernalsuite.aswm.SlimeNMSBridgeImpl diff --git a/patches/server/0001-Build-changes.patch b/patches/server/0002-Build-changes.patch similarity index 92% rename from patches/server/0001-Build-changes.patch rename to patches/server/0002-Build-changes.patch index 14b15fc..60faacb 100644 --- a/patches/server/0001-Build-changes.patch +++ b/patches/server/0002-Build-changes.patch @@ -5,13 +5,13 @@ Subject: [PATCH] Build changes diff --git a/build.gradle.kts b/build.gradle.kts -index fb98936bb8a5488db75d676c5bcb4060597fbbf8..904bf48288e0865db9bbe8d2ca183b8385f73407 100644 +index 2143180a92ec6d0c0eba5559dd5497291348fdfa..91c9a0239b8a4d821dd83ea32d971a0a63e5929a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts -@@ -13,8 +13,12 @@ configurations.named(log4jPlugins.compileClasspathConfigurationName) { - val alsoShade: Configuration by configurations.creating +@@ -14,8 +14,12 @@ val alsoShade: Configuration by configurations.creating dependencies { + implementation(project(":aswm-core")) - implementation(project(":paper-api")) - implementation(project(":paper-mojangapi")) + // Scissors start @@ -23,7 +23,7 @@ index fb98936bb8a5488db75d676c5bcb4060597fbbf8..904bf48288e0865db9bbe8d2ca183b83 // Paper start implementation("org.jline:jline-terminal-jansi:3.21.0") implementation("net.minecrell:terminalconsoleappender:1.3.0") -@@ -68,11 +72,19 @@ tasks.jar { +@@ -69,11 +73,19 @@ tasks.jar { val gitHash = git("rev-parse", "--short=7", "HEAD").getText().trim() val implementationVersion = System.getenv("BUILD_NUMBER") ?: "\"$gitHash\"" val date = git("show", "-s", "--format=%ci", gitHash).getText().trim() // Paper @@ -45,7 +45,7 @@ index fb98936bb8a5488db75d676c5bcb4060597fbbf8..904bf48288e0865db9bbe8d2ca183b83 "Implementation-Vendor" to date, // Paper "Specification-Title" to "Bukkit", "Specification-Version" to project.version, -@@ -149,7 +161,7 @@ fun TaskContainer.registerRunTask( +@@ -150,7 +162,7 @@ fun TaskContainer.registerRunTask( name: String, block: JavaExec.() -> Unit ): TaskProvider = register(name) { @@ -95,10 +95,10 @@ index c5d5648f4ca603ef2b1df723b58f9caf4dd3c722..21ded7c14c56a40feaa7741131be5166 .completer(new ConsoleCommandCompleter(this.server)) .option(LineReader.Option.COMPLETE_IN_WORD, true); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3238cbcba567b1242c77e41f6b6f19a8d157fb4e..dd4dc6b29e37a4db459779fa8d4adc231e44d144 100644 +index 9d636e893885a411cde5fb72f9a79da0d2f7cbf9..c69729fb08819860f366fbe578213a8327b82569 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1693,7 +1693,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { diff --git a/patches/server/0042-Fix-component-extra-empty-array-exploit.patch b/patches/server/0043-Fix-component-extra-empty-array-exploit.patch similarity index 100% rename from patches/server/0042-Fix-component-extra-empty-array-exploit.patch rename to patches/server/0043-Fix-component-extra-empty-array-exploit.patch diff --git a/patches/server/0043-Add-depth-limit-to-Component-deserializer.patch b/patches/server/0044-Add-depth-limit-to-Component-deserializer.patch similarity index 100% rename from patches/server/0043-Add-depth-limit-to-Component-deserializer.patch rename to patches/server/0044-Add-depth-limit-to-Component-deserializer.patch diff --git a/patches/server/0044-Implement-command-block-events.patch b/patches/server/0045-Implement-command-block-events.patch similarity index 100% rename from patches/server/0044-Implement-command-block-events.patch rename to patches/server/0045-Implement-command-block-events.patch diff --git a/patches/server/0045-Add-depth-limit-to-SNBT.patch b/patches/server/0046-Add-depth-limit-to-SNBT.patch similarity index 100% rename from patches/server/0045-Add-depth-limit-to-SNBT.patch rename to patches/server/0046-Add-depth-limit-to-SNBT.patch diff --git a/patches/server/0046-Limit-beacon-effectRange.patch b/patches/server/0047-Limit-beacon-effectRange.patch similarity index 100% rename from patches/server/0046-Limit-beacon-effectRange.patch rename to patches/server/0047-Limit-beacon-effectRange.patch diff --git a/patches/server/0047-Improve-validation-of-ResourceLocations.patch b/patches/server/0048-Improve-validation-of-ResourceLocations.patch similarity index 100% rename from patches/server/0047-Improve-validation-of-ResourceLocations.patch rename to patches/server/0048-Improve-validation-of-ResourceLocations.patch diff --git a/patches/server/0048-Don-t-log-on-too-many-chained-updates.patch b/patches/server/0049-Don-t-log-on-too-many-chained-updates.patch similarity index 100% rename from patches/server/0048-Don-t-log-on-too-many-chained-updates.patch rename to patches/server/0049-Don-t-log-on-too-many-chained-updates.patch diff --git a/patches/server/0049-Fix-packet-related-lag-exploits.patch b/patches/server/0050-Fix-packet-related-lag-exploits.patch similarity index 100% rename from patches/server/0049-Fix-packet-related-lag-exploits.patch rename to patches/server/0050-Fix-packet-related-lag-exploits.patch diff --git a/patches/server/0050-Limit-save-data-for-Bees-and-Vexes.patch b/patches/server/0051-Limit-save-data-for-Bees-and-Vexes.patch similarity index 100% rename from patches/server/0050-Limit-save-data-for-Bees-and-Vexes.patch rename to patches/server/0051-Limit-save-data-for-Bees-and-Vexes.patch diff --git a/patches/server/0051-Mute-invalid-attributes.patch b/patches/server/0052-Mute-invalid-attributes.patch similarity index 100% rename from patches/server/0051-Mute-invalid-attributes.patch rename to patches/server/0052-Mute-invalid-attributes.patch diff --git a/patches/server/0052-Mute-invalid-Enderdragon-phases.patch b/patches/server/0053-Mute-invalid-Enderdragon-phases.patch similarity index 100% rename from patches/server/0052-Mute-invalid-Enderdragon-phases.patch rename to patches/server/0053-Mute-invalid-Enderdragon-phases.patch diff --git a/patches/server/0053-Don-t-return-null-Components-in-the-Component-codec.patch b/patches/server/0054-Don-t-return-null-Components-in-the-Component-codec.patch similarity index 100% rename from patches/server/0053-Don-t-return-null-Components-in-the-Component-codec.patch rename to patches/server/0054-Don-t-return-null-Components-in-the-Component-codec.patch diff --git a/settings.gradle.kts b/settings.gradle.kts index 6d38822..14941b4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,4 +7,4 @@ pluginManagement { rootProject.name = "Scissors" -include("Scissors-API", "Scissors-Server") +include("aswm-api", "aswm-core", "Scissors-API", "Scissors-Server") diff --git a/submodules/AdvancedSlimePaper b/submodules/AdvancedSlimePaper new file mode 160000 index 0000000..3934c52 --- /dev/null +++ b/submodules/AdvancedSlimePaper @@ -0,0 +1 @@ +Subproject commit 3934c520664e7e8f61f3a21463017ab2efac64b6 From 8f2be500b5c336ea91ea8d0ec47ecfb91da4c723 Mon Sep 17 00:00:00 2001 From: Luna <90072930+LunaWasFlaggedAgain@users.noreply.github.com> Date: Tue, 22 Aug 2023 22:11:53 -0300 Subject: [PATCH 02/10] Fix Github actions (#138) --- .github/workflows/build.yml | 2 ++ .gitmodules | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a596c8..52915f6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,8 @@ jobs: steps: - name: Checkout Git Repository uses: actions/checkout@v3 + with: + submodules: 'true' - name: Validate Gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Setup Gradle diff --git a/.gitmodules b/.gitmodules index e9b11ec..939fbf2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "submodules/AdvancedSlimePaper"] path = submodules/AdvancedSlimePaper - url = git@github.com:InfernalSuite/AdvancedSlimePaper.git + url = https://github.com/InfernalSuite/AdvancedSlimePaper.git From cbd52d38d914329827594ea565a96bdbbf10a512 Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Tue, 22 Aug 2023 23:07:02 -0500 Subject: [PATCH 03/10] Jenkins fix (#139) * Update Jenkinsfile * Update Jenkinsfile --- Jenkinsfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9f4eb85..24ba777 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,6 +4,11 @@ pipeline { GITHUB_BRANCH = "${BRANCH_NAME}" } stages { + stage("clone") { + steps { + checkout scmGit(branches: [[name: '*/slime/1.20.1']], extensions: [submodule(parentCredentials: true, recursiveSubmodules: true, reference: '')], userRemoteConfigs: [[url: 'https://github.com/AtlasMediaGroup/Scissors']]) + } + } stage('applyPatches') { steps { withGradle { @@ -40,4 +45,4 @@ pipeline { cleanWs() } } -} \ No newline at end of file +} From 8bc2c95edb58289d6b9e20c1096240dad9b285a2 Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Wed, 23 Aug 2023 16:35:23 -0500 Subject: [PATCH 04/10] Update Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 24ba777..d20004b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,7 +6,7 @@ pipeline { stages { stage("clone") { steps { - checkout scmGit(branches: [[name: '*/slime/1.20.1']], extensions: [submodule(parentCredentials: true, recursiveSubmodules: true, reference: '')], userRemoteConfigs: [[url: 'https://github.com/AtlasMediaGroup/Scissors']]) + checkout scmGit(branches: [[name: '*/slime/1.20.1']], extensions: [submodule(parentCredentials: true, recursiveSubmodules: true, reference: 'https://github.com/InfernalSuite/AdvancedSlimePaper')], userRemoteConfigs: [[url: 'https://github.com/AtlasMediaGroup/Scissors']]) } } stage('applyPatches') { From 1bbbb18b2e605dbf0d182aa3154350b42c8beb69 Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Wed, 23 Aug 2023 16:37:40 -0500 Subject: [PATCH 05/10] try this --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index d20004b..c548859 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,7 +6,7 @@ pipeline { stages { stage("clone") { steps { - checkout scmGit(branches: [[name: '*/slime/1.20.1']], extensions: [submodule(parentCredentials: true, recursiveSubmodules: true, reference: 'https://github.com/InfernalSuite/AdvancedSlimePaper')], userRemoteConfigs: [[url: 'https://github.com/AtlasMediaGroup/Scissors']]) + sh 'git submodule update --recursive' } } stage('applyPatches') { From d013cfde885de6b8df046f2eb8f2dab5a2165c93 Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Wed, 23 Aug 2023 16:43:37 -0500 Subject: [PATCH 06/10] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c548859..425daf9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,9 +4,9 @@ pipeline { GITHUB_BRANCH = "${BRANCH_NAME}" } stages { - stage("clone") { + stage('checkout') { steps { - sh 'git submodule update --recursive' + checkout scmGit(branches: [[name: '*/slime/1.20.1']], extensions: [submodule(parentCredentials: true, recursiveSubmodules: true, reference: 'https://github.com/InfernalSuite/AdvancedSlimePaper')], userRemoteConfigs: [[url: 'https://github.com/AtlasMediaGroup/Scissors']]) } } stage('applyPatches') { From 79705ff262b108e07c74e2276337220c6ee54553 Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Wed, 23 Aug 2023 16:44:58 -0500 Subject: [PATCH 07/10] Update Jenkinsfile --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 425daf9..242cc8e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,5 @@ pipeline { + options { skipDefaultCheckout() } agent any environment { GITHUB_BRANCH = "${BRANCH_NAME}" From d6432c162fcfd2a4653f4660cbf6e28289449407 Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Thu, 24 Aug 2023 16:35:32 -0500 Subject: [PATCH 08/10] Update Paper --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4e22167..f6b67f6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group=me.totalfreedom.scissors version=1.20.1-R0.1-SNAPSHOT mcVersion=1.20.1 -paperRef=3716832282a136dbbd29ab04d1a37ae88ac3726e +paperRef=0c8882f7f9a39cadb6e3ca8b94bed7ab9b41c710 org.gradle.caching=true org.gradle.parallel=true From 8226a3b03a89738a7039f1cefdbdfcb89011984e Mon Sep 17 00:00:00 2001 From: Taahh Date: Sat, 26 Aug 2023 06:38:40 -0700 Subject: [PATCH 09/10] Update slime branch (#142) * Update Paper * Add length limit to note block sound (#141) Reported by @NekosAreKawaii --------- Co-authored-by: Telesphoreo Co-authored-by: allinkdev <44676012+allinkdev@users.noreply.github.com> --- ...Add-length-limit-to-note-block-sound.patch | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 patches/server/0054-Add-length-limit-to-note-block-sound.patch diff --git a/patches/server/0054-Add-length-limit-to-note-block-sound.patch b/patches/server/0054-Add-length-limit-to-note-block-sound.patch new file mode 100644 index 0000000..46c6329 --- /dev/null +++ b/patches/server/0054-Add-length-limit-to-note-block-sound.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Allink +Date: Fri, 25 Aug 2023 11:51:47 +0100 +Subject: [PATCH] Add length limit to note block sound + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +index a2fc2c0437999dd09f080eafe8ea466b16cdf57b..0dcf1d7e041477fe31dce4b4ee707399520d30f2 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +@@ -80,7 +80,7 @@ public class SkullBlockEntity extends BlockEntity { + } + + if (nbt.contains("note_block_sound", 8)) { +- this.noteBlockSound = ResourceLocation.tryParse(nbt.getString("note_block_sound")); ++ this.noteBlockSound = ResourceLocation.tryParse(StringUtil.truncateStringIfNecessary(nbt.getString("note_block_sound"), 32767, false)); // Scissors - Add length limit to note block sound + } + + } From ff6a9c5ff616067f2c9cd5132250b706b4afaa4e Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Sat, 26 Aug 2023 18:29:50 -0500 Subject: [PATCH 10/10] Update Paper --- README.md | 2 +- gradle.properties | 2 +- ...5-Don-t-return-null-Components-in-the-Component-codec.patch} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename patches/server/{0054-Don-t-return-null-Components-in-the-Component-codec.patch => 0055-Don-t-return-null-Components-in-the-Component-codec.patch} (100%) diff --git a/README.md b/README.md index adab081..67945cc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Scissors [![Build Status](https://ci.scissors.gg/job/Scissors/job/1.20.1/badge/icon)](https://ci.scissors.gg/job/Scissors/job/1.20.1/) +# Scissors [![Build Status](https://ci.plex.us.org/job/Scissors/job/slime%252F1.20.1/badge/icon)](https://ci.plex.us.org/job/Scissors/job/slime%252F1.20.1/) Scissors is a fork of Paper that aims to fix exploits possible in Creative Mode. Many of these exploits are ones that Paper's own team has either refused to fix or would have. diff --git a/gradle.properties b/gradle.properties index f6b67f6..b75d112 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group=me.totalfreedom.scissors version=1.20.1-R0.1-SNAPSHOT mcVersion=1.20.1 -paperRef=0c8882f7f9a39cadb6e3ca8b94bed7ab9b41c710 +paperRef=0c0a480d82132dca0e4bb7a2275c9363430e544b org.gradle.caching=true org.gradle.parallel=true diff --git a/patches/server/0054-Don-t-return-null-Components-in-the-Component-codec.patch b/patches/server/0055-Don-t-return-null-Components-in-the-Component-codec.patch similarity index 100% rename from patches/server/0054-Don-t-return-null-Components-in-the-Component-codec.patch rename to patches/server/0055-Don-t-return-null-Components-in-the-Component-codec.patch