From 40a91ca05ea17505156665459e44a01b75fa9c56 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] 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