From 9ae1def66048a7c7bf0231d36c2b8d88c0a917f4 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sat, 30 Dec 2023 14:34:54 +0100 Subject: [PATCH] Add back Level data attachments (#435) The typical use case is to store non-persistent client-side data, or have similar handling for data on both the client and server side. The implementation for persistence is basically the same as for the pre-1.20.2 capabilities. --- .../server/level/ServerLevel.java.patch | 9 ++++ .../minecraft/world/level/Level.java.patch | 2 +- .../neoforge/attachment/AttachmentType.java | 6 ++- .../attachment/LevelAttachmentsSavedData.java | 49 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/neoforged/neoforge/attachment/LevelAttachmentsSavedData.java diff --git a/patches/net/minecraft/server/level/ServerLevel.java.patch b/patches/net/minecraft/server/level/ServerLevel.java.patch index 82396345cf..a6094dac99 100644 --- a/patches/net/minecraft/server/level/ServerLevel.java.patch +++ b/patches/net/minecraft/server/level/ServerLevel.java.patch @@ -9,6 +9,15 @@ private final StructureManager structureManager; private final StructureCheck structureCheck; private final boolean tickTime; +@@ -281,6 +_,8 @@ + this.randomSequences = Objects.requireNonNullElseGet( + p_288977_, () -> this.getDataStorage().computeIfAbsent(RandomSequences.factory(i), "random_sequences") + ); ++ ++ net.neoforged.neoforge.attachment.LevelAttachmentsSavedData.init(this); + } + + @Deprecated @@ -325,7 +_,7 @@ if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { diff --git a/patches/net/minecraft/world/level/Level.java.patch b/patches/net/minecraft/world/level/Level.java.patch index 063b4aae2c..0ec1792350 100644 --- a/patches/net/minecraft/world/level/Level.java.patch +++ b/patches/net/minecraft/world/level/Level.java.patch @@ -5,7 +5,7 @@ import net.minecraft.world.scores.Scoreboard; -public abstract class Level implements LevelAccessor, AutoCloseable { -+public abstract class Level implements LevelAccessor, AutoCloseable, net.neoforged.neoforge.common.extensions.ILevelExtension { ++public abstract class Level extends net.neoforged.neoforge.attachment.AttachmentHolder implements LevelAccessor, AutoCloseable, net.neoforged.neoforge.common.extensions.ILevelExtension { public static final Codec> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION); public static final ResourceKey OVERWORLD = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("overworld")); public static final ResourceKey NETHER = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("the_nether")); diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java index 02669a0928..9590be6d42 100644 --- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java +++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java @@ -12,6 +12,7 @@ import net.minecraft.nbt.Tag; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; import net.neoforged.neoforge.common.util.INBTSerializable; @@ -38,13 +39,16 @@ *
  • Serializable item stack attachments are copied when an item stack is copied.
  • *
  • Serializable item stack attachments must match for item stack comparison to succeed.
  • * + *

    {@link Level}-exclusive behavior:

    + *
      + *
    • (nothing)
    • + *
    *

    {@link LevelChunk}-exclusive behavior:

    *
      *
    • Modifications to attachments should be followed by a call to {@link LevelChunk#setUnsaved(boolean)}.
    • *
    */ // TODO Future work: maybe add copy handlers for stacks and entities, to customize copying behavior (instead of serializing, copying the NBT, deserializing). -// TODO Future work: maybe add custom comparison handlers for item stacks. public final class AttachmentType { final Supplier defaultValueSupplier; @Nullable diff --git a/src/main/java/net/neoforged/neoforge/attachment/LevelAttachmentsSavedData.java b/src/main/java/net/neoforged/neoforge/attachment/LevelAttachmentsSavedData.java new file mode 100644 index 0000000000..bba9a56595 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/attachment/LevelAttachmentsSavedData.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.attachment; + +import java.util.Objects; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class LevelAttachmentsSavedData extends SavedData { + private static final String NAME = "neoforge_data_attachments"; + + public static void init(ServerLevel level) { + var factory = new SavedData.Factory<>( + () -> new LevelAttachmentsSavedData(level), + tag -> new LevelAttachmentsSavedData(level, tag)); + // Querying the attachment a single time is enough to initialize it, + // and make sure it gets saved when the level is saved. + level.getDataStorage().computeIfAbsent(factory, NAME); + } + + private final ServerLevel level; + + public LevelAttachmentsSavedData(ServerLevel level) { + this.level = level; + } + + public LevelAttachmentsSavedData(ServerLevel level, CompoundTag tag) { + this.level = level; + level.deserializeAttachments(tag); + } + + @Override + public CompoundTag save(CompoundTag tag) { + // Make sure we don't return null + return Objects.requireNonNullElseGet(level.serializeAttachments(), CompoundTag::new); + } + + @Override + public boolean isDirty() { + // Always re-save + return true; + } +}