Skip to content

Commit

Permalink
Add back Level data attachments (neoforged#435)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Technici4n authored Dec 30, 2023
1 parent 30ac00e commit 9ae1def
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 2 deletions.
9 changes: 9 additions & 0 deletions patches/net/minecraft/server/level/ServerLevel.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
2 changes: 1 addition & 1 deletion patches/net/minecraft/world/level/Level.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("overworld"));
public static final ResourceKey<Level> NETHER = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("the_nether"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,13 +39,16 @@
* <li>Serializable item stack attachments are copied when an item stack is copied.</li>
* <li>Serializable item stack attachments must match for item stack comparison to succeed.</li>
* </ul>
* <h3>{@link Level}-exclusive behavior:</h3>
* <ul>
* <li>(nothing)</li>
* </ul>
* <h3>{@link LevelChunk}-exclusive behavior:</h3>
* <ul>
* <li>Modifications to attachments should be followed by a call to {@link LevelChunk#setUnsaved(boolean)}.</li>
* </ul>
*/
// 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<T> {
final Supplier<T> defaultValueSupplier;
@Nullable
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 9ae1def

Please sign in to comment.