mirror of
https://github.com/FabricMC/fabric.git
synced 2024-11-14 19:25:23 -05:00
Extend Data Attachment API to ProtoChunk (#3548)
* allow data-attachment on ProtoChunks
- moved interfaceInjection from WorldChunk to Chunk
- dataAttachment saving on ProtoChunks in ChunkSerializer
- copy attachment from ProtoChunk to WorldChunk on creation.
- make WrapperProtoChunk wrap attachment calls to WorldChunk
* add test for data-attachment on ProtoChunks, and extend testmod.
* code style and license headers
* fix typos in javadoc
* extend testmod to test setting attachment during worldgen.
* code formatting
* fix testmod: don't crash when feature isn't placed (i.e. on GameTest server)
* add warning when adding persistent attachment to chunk with status EMPTY.
* update javadoc
* update javadoc to reference ServerLivingEntityEvents#MOB_CONVERSION
(cherry picked from commit 32782cfdc7
)
This commit is contained in:
parent
6e2af442ba
commit
6a60afdd2e
17 changed files with 313 additions and 44 deletions
|
@ -7,5 +7,6 @@ moduleDependencies(project, [
|
|||
])
|
||||
|
||||
testDependencies(project, [
|
||||
':fabric-lifecycle-events-v1'
|
||||
':fabric-lifecycle-events-v1',
|
||||
':fabric-biome-api-v1'
|
||||
])
|
||||
|
|
|
@ -38,7 +38,7 @@ abstract class ClientPlayNetworkHandlerMixin {
|
|||
/*
|
||||
* The KEEP_ATTRIBUTES flag is not set on a death respawn, and set in all other cases
|
||||
*/
|
||||
AttachmentTargetImpl.copyOnRespawn(oldPlayer, newPlayer, !packet.hasFlag(PlayerRespawnS2CPacket.KEEP_ATTRIBUTES));
|
||||
AttachmentTargetImpl.transfer(oldPlayer, newPlayer, !packet.hasFlag(PlayerRespawnS2CPacket.KEEP_ATTRIBUTES));
|
||||
init.call(newPlayer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,14 +28,14 @@ import net.minecraft.block.entity.BlockEntity;
|
|||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
|
||||
/**
|
||||
* Marks all objects on which data can be attached using {@link AttachmentType}s.
|
||||
*
|
||||
* <p>Fabric implements this on {@link Entity}, {@link BlockEntity}, {@link ServerWorld} and {@link WorldChunk} via mixin.</p>
|
||||
* <p>Fabric implements this on {@link Entity}, {@link BlockEntity}, {@link ServerWorld} and {@link Chunk} via mixin.</p>
|
||||
*
|
||||
* <p>Note about {@link BlockEntity} and {@link WorldChunk} targets: these objects need to be notified of changes to their
|
||||
* <p>Note about {@link BlockEntity} and {@link Chunk} targets: these objects need to be notified of changes to their
|
||||
* state (using {@link BlockEntity#markDirty()} and {@link Chunk#setNeedsSaving(boolean)} respectively), otherwise the modifications will not take effect properly.
|
||||
* The {@link #setAttached(AttachmentType, Object)} method handles this automatically, but this needs to be done manually
|
||||
* when attached data is mutable, for example:
|
||||
|
@ -55,6 +55,12 @@ import net.minecraft.world.chunk.WorldChunk;
|
|||
* which takes care of all vanilla types. However, modded block entities may be coded differently, so be wary of this
|
||||
* when attaching data to modded block entities.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note about {@link Chunk} targets with {@link ChunkStatus#EMPTY}: These chunks are not saved unless the generation
|
||||
* progresses to at least {@link ChunkStatus#STRUCTURE_STARTS}. Therefore, persistent attachments to those chunks may not
|
||||
* be saved. The {@link #setAttached(AttachmentType, Object)} method will log a warning when this is attempted.
|
||||
* </p>
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@ApiStatus.NonExtendable
|
||||
|
|
|
@ -23,11 +23,14 @@ import org.jetbrains.annotations.ApiStatus;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.mob.MobEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ProtoChunk;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
|
||||
/**
|
||||
|
@ -39,13 +42,15 @@ import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
|||
* immutable types. More generally, different attachments <i>must not</i> share mutable state, and it is <i>strongly advised</i>
|
||||
* for attachments not to hold internal references to their target. See the following note on entity targets.</p>
|
||||
*
|
||||
* <p>Note on {@link Entity} targets: in several instances, the name needs to copy data from one {@link Entity} to another.
|
||||
* These are player respawning, mob conversion, return from the End and cross-world entity teleportation. By default,
|
||||
* attachments are simply copied wholesale, up to {@link #copyOnDeath()}. Since one entity instance is discarded,
|
||||
* an attachment that keeps a reference to an {@link Entity} instance can and will break unexpectedly. If,
|
||||
* for whatever reason, keeping to reference to the target entity is absolutely necessary, be sure to use
|
||||
* {@link ServerPlayerEvents#COPY_FROM}, {@link ServerEntityWorldChangeEvents#AFTER_ENTITY_CHANGE_WORLD}
|
||||
* and a mixin into {@link MobEntity#convertTo(EntityType, boolean)} to implement custom copying logic.</p>
|
||||
* <p>Note on {@link Entity} and {@link Chunk} targets: in several instances, the game needs to copy data from one instance to another.
|
||||
* These are player respawning, mob conversion, return from the End, cross-world entity teleportation, and conversion of a {@link ProtoChunk} to
|
||||
* {@link WorldChunk}. By default, attachments are simply copied wholesale, up to {@link #copyOnDeath()}. Since one instance is discarded,
|
||||
* an attachment that keeps a reference to an {@link Entity} or {@link ProtoChunk} instance can and will break unexpectedly. If,
|
||||
* for whatever reason, keeping a reference to the target is absolutely necessary, be sure to implement custom copying logic.
|
||||
* For {@link Entity} targets, use {@link ServerPlayerEvents#COPY_FROM}, {@link ServerEntityWorldChangeEvents#AFTER_ENTITY_CHANGE_WORLD},
|
||||
* and {@link ServerLivingEntityEvents#MOB_CONVERSION}. For {@link Chunk} targets, mixin into
|
||||
* {@link WorldChunk#WorldChunk(ServerWorld, ProtoChunk, WorldChunk.EntityLoader)}.
|
||||
* </p>
|
||||
*
|
||||
* @param <A> type of the attached data. It is encouraged for this to be an immutable type.
|
||||
*/
|
||||
|
|
|
@ -16,23 +16,28 @@
|
|||
|
||||
package net.fabricmc.fabric.impl.attachment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
|
||||
public class AttachmentEntrypoint implements ModInitializer {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("fabric-data-attachment-api-v1");
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
ServerPlayerEvents.COPY_FROM.register((oldPlayer, newPlayer, alive) ->
|
||||
AttachmentTargetImpl.copyOnRespawn(oldPlayer, newPlayer, !alive)
|
||||
AttachmentTargetImpl.transfer(oldPlayer, newPlayer, !alive)
|
||||
);
|
||||
ServerEntityWorldChangeEvents.AFTER_ENTITY_CHANGE_WORLD.register(((originalEntity, newEntity, origin, destination) ->
|
||||
AttachmentTargetImpl.copyOnRespawn(originalEntity, newEntity, false))
|
||||
AttachmentTargetImpl.transfer(originalEntity, newEntity, false))
|
||||
);
|
||||
// using the corresponding player event is unnecessary as no new instance is created
|
||||
ServerLivingEntityEvents.MOB_CONVERSION.register((previous, converted, keepEquipment) ->
|
||||
AttachmentTargetImpl.copyOnRespawn(previous, converted, true)
|
||||
AttachmentTargetImpl.transfer(previous, converted, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,13 @@ import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
|
|||
|
||||
public interface AttachmentTargetImpl extends AttachmentTarget {
|
||||
/**
|
||||
* Copies entity attachments when it is respawned and a new instance is created.
|
||||
* Is triggered on player respawn, entity conversion, return from the End or cross-world entity teleportation.
|
||||
* Copies attachments from the original to the target. This is used when a ProtoChunk is converted to a
|
||||
* WorldChunk, and when an entity is respawned and a new instance is created. For entity respawns, it is
|
||||
* triggered on player respawn, entity conversion, return from the End, or cross-world entity teleportation.
|
||||
* In the first two cases, only the attachments with {@link AttachmentType#copyOnDeath()} will be transferred.
|
||||
*/
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static void copyOnRespawn(AttachmentTarget original, AttachmentTarget target, boolean isDeath) {
|
||||
static void transfer(AttachmentTarget original, AttachmentTarget target, boolean isDeath) {
|
||||
Map<AttachmentType<?>, ?> attachments = ((AttachmentTargetImpl) original).fabric_getAttachments();
|
||||
|
||||
if (attachments == null) {
|
||||
|
|
|
@ -28,13 +28,14 @@ import net.minecraft.nbt.NbtCompound;
|
|||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
|
||||
import net.fabricmc.fabric.impl.attachment.AttachmentEntrypoint;
|
||||
import net.fabricmc.fabric.impl.attachment.AttachmentSerializingImpl;
|
||||
import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl;
|
||||
|
||||
@Mixin({BlockEntity.class, Entity.class, World.class, WorldChunk.class})
|
||||
@Mixin({BlockEntity.class, Entity.class, World.class, Chunk.class})
|
||||
abstract class AttachmentTargetsMixin implements AttachmentTargetImpl {
|
||||
@Nullable
|
||||
private IdentityHashMap<AttachmentType<?>, Object> fabric_dataAttachments = null;
|
||||
|
@ -55,8 +56,12 @@ abstract class AttachmentTargetsMixin implements AttachmentTargetImpl {
|
|||
|
||||
if (thisObject instanceof BlockEntity) {
|
||||
((BlockEntity) thisObject).markDirty();
|
||||
} else if (thisObject instanceof WorldChunk) {
|
||||
} else if (thisObject instanceof Chunk) {
|
||||
((Chunk) thisObject).setNeedsSaving(true);
|
||||
|
||||
if (type.isPersistent() && ((Chunk) thisObject).getStatus().equals(ChunkStatus.EMPTY)) {
|
||||
AttachmentEntrypoint.LOGGER.warn("Attaching persistent attachment {} to chunk with chunk status EMPTY. Attachment might be discarded.", type.identifier());
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
|
|
|
@ -27,7 +27,7 @@ import net.minecraft.server.world.ServerWorld;
|
|||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.ChunkSerializer;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
import net.minecraft.world.chunk.ProtoChunk;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import net.minecraft.world.poi.PointOfInterestStorage;
|
||||
|
||||
|
@ -42,18 +42,28 @@ abstract class ChunkSerializerMixin {
|
|||
),
|
||||
method = "deserialize"
|
||||
)
|
||||
private static WorldChunk readChunkAttachments(WorldChunk chunk, ServerWorld world, PointOfInterestStorage poiStorage, ChunkPos chunkPos, NbtCompound nbt) {
|
||||
private static WorldChunk readWorldChunkAttachments(WorldChunk chunk, ServerWorld world, PointOfInterestStorage poiStorage, ChunkPos chunkPos, NbtCompound nbt) {
|
||||
((AttachmentTargetImpl) chunk).fabric_readAttachmentsFromNbt(nbt, world.getRegistryManager());
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
at = @At(
|
||||
value = "NEW",
|
||||
target = "net/minecraft/world/chunk/ProtoChunk"
|
||||
),
|
||||
method = "deserialize"
|
||||
)
|
||||
private static ProtoChunk readProtoChunkAttachments(ProtoChunk chunk, ServerWorld world, PointOfInterestStorage poiStorage, ChunkPos chunkPos, NbtCompound nbt) {
|
||||
((AttachmentTargetImpl) chunk).fabric_readAttachmentsFromNbt(nbt);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
at = @At("RETURN"),
|
||||
method = "serialize"
|
||||
)
|
||||
private static void writeChunkAttachments(ServerWorld world, Chunk chunk, CallbackInfoReturnable<NbtCompound> cir) {
|
||||
if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.LEVELCHUNK) {
|
||||
((AttachmentTargetImpl) chunk).fabric_writeAttachmentsToNbt(cir.getReturnValue(), world.getRegistryManager());
|
||||
}
|
||||
((AttachmentTargetImpl) chunk).fabric_writeAttachmentsToNbt(cir.getReturnValue(), world.getRegistryManager());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.attachment;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.world.chunk.ProtoChunk;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentTarget;
|
||||
import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl;
|
||||
|
||||
@Mixin(WorldChunk.class)
|
||||
public class WorldChunkMixin {
|
||||
@Inject(
|
||||
method = "<init>(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/world/chunk/ProtoChunk;Lnet/minecraft/world/chunk/WorldChunk$EntityLoader;)V",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
public void transferProtoChunkAttachement(ServerWorld world, ProtoChunk protoChunk, WorldChunk.EntityLoader entityLoader, CallbackInfo ci) {
|
||||
AttachmentTargetImpl.transfer(protoChunk, (AttachmentTarget) this, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.attachment;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import net.minecraft.world.chunk.WrapperProtoChunk;
|
||||
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
|
||||
import net.fabricmc.fabric.impl.attachment.AttachmentTargetImpl;
|
||||
|
||||
@Mixin(WrapperProtoChunk.class)
|
||||
public class WrapperProtoChunkMixin implements AttachmentTargetImpl {
|
||||
@Shadow
|
||||
@Final
|
||||
private WorldChunk wrapped;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T getAttached(AttachmentType<T> type) {
|
||||
return this.wrapped.getAttached(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T setAttached(AttachmentType<T> type, @Nullable T value) {
|
||||
return this.wrapped.setAttached(type, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAttached(AttachmentType<?> type) {
|
||||
return this.wrapped.hasAttached(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_writeAttachmentsToNbt(NbtCompound nbt) {
|
||||
((AttachmentTargetImpl) this.wrapped).fabric_writeAttachmentsToNbt(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabric_readAttachmentsFromNbt(NbtCompound nbt) {
|
||||
((AttachmentTargetImpl) this.wrapped).fabric_readAttachmentsFromNbt(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fabric_hasPersistentAttachments() {
|
||||
return ((AttachmentTargetImpl) this.wrapped).fabric_hasPersistentAttachments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<AttachmentType<?>, ?> fabric_getAttachments() {
|
||||
return ((AttachmentTargetImpl) this.wrapped).fabric_getAttachments();
|
||||
}
|
||||
}
|
|
@ -8,7 +8,9 @@
|
|||
"BlockEntityUpdateS2CPacketMixin",
|
||||
"ChunkSerializerMixin",
|
||||
"EntityMixin",
|
||||
"ServerWorldMixin"
|
||||
"ServerWorldMixin",
|
||||
"WorldChunkMixin",
|
||||
"WrapperProtoChunkMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"fabric-api:module-lifecycle": "experimental",
|
||||
"loom:injected_interfaces": {
|
||||
"net/minecraft/class_2586": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"],
|
||||
"net/minecraft/class_2818": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"],
|
||||
"net/minecraft/class_2791": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"],
|
||||
"net/minecraft/class_1297": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"],
|
||||
"net/minecraft/class_3218": ["net/fabricmc/fabric/api/attachment/v1/AttachmentTarget"]
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import net.minecraft.nbt.NbtElement;
|
|||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.chunk.ProtoChunk;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry;
|
||||
|
@ -82,8 +83,9 @@ public class CommonAttachmentTests {
|
|||
Entity entity = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
BlockEntity blockEntity = mock(BlockEntity.class, CALLS_REAL_METHODS);
|
||||
WorldChunk worldChunk = mock(WorldChunk.class, CALLS_REAL_METHODS);
|
||||
ProtoChunk protoChunk = mock(ProtoChunk.class, CALLS_REAL_METHODS);
|
||||
|
||||
for (AttachmentTarget target : new AttachmentTarget[]{serverWorld, entity, blockEntity, worldChunk}) {
|
||||
for (AttachmentTarget target : new AttachmentTarget[]{serverWorld, entity, blockEntity, worldChunk, protoChunk}) {
|
||||
testForTarget(target, basic);
|
||||
}
|
||||
}
|
||||
|
@ -161,8 +163,8 @@ public class CommonAttachmentTests {
|
|||
Entity respawnTarget = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
Entity nonRespawnTarget = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
|
||||
AttachmentTargetImpl.copyOnRespawn(original, respawnTarget, true);
|
||||
AttachmentTargetImpl.copyOnRespawn(original, nonRespawnTarget, false);
|
||||
AttachmentTargetImpl.transfer(original, respawnTarget, true);
|
||||
AttachmentTargetImpl.transfer(original, nonRespawnTarget, false);
|
||||
assertTrue(respawnTarget.hasAttached(copiedOnRespawn));
|
||||
assertFalse(respawnTarget.hasAttached(notCopiedOnRespawn));
|
||||
assertTrue(nonRespawnTarget.hasAttached(copiedOnRespawn));
|
||||
|
|
|
@ -20,13 +20,27 @@ import com.mojang.serialization.Codec;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
import net.minecraft.world.chunk.ProtoChunk;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import net.minecraft.world.chunk.WrapperProtoChunk;
|
||||
import net.minecraft.world.gen.GenerationStep;
|
||||
import net.minecraft.world.gen.feature.DefaultFeatureConfig;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry;
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
|
||||
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
|
||||
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
|
||||
public class AttachmentTestMod implements ModInitializer {
|
||||
|
@ -36,33 +50,78 @@ public class AttachmentTestMod implements ModInitializer {
|
|||
new Identifier(MOD_ID, "persistent"),
|
||||
Codec.STRING
|
||||
);
|
||||
public static final AttachmentType<String> FEATURE_ATTACHMENT = AttachmentRegistry.create(
|
||||
new Identifier(MOD_ID, "feature")
|
||||
);
|
||||
|
||||
public static final ChunkPos FAR_CHUNK_POS = new ChunkPos(30, 0);
|
||||
|
||||
private boolean firstLaunch = true;
|
||||
public static boolean featurePlaced = false;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
Registry.register(Registries.FEATURE, new Identifier(MOD_ID, "set_attachment"), new SetAttachmentFeature(DefaultFeatureConfig.CODEC));
|
||||
|
||||
BiomeModifications.addFeature(
|
||||
BiomeSelectors.foundInOverworld(),
|
||||
GenerationStep.Feature.VEGETAL_DECORATION,
|
||||
RegistryKey.of(RegistryKeys.PLACED_FEATURE, new Identifier(MOD_ID, "set_attachment"))
|
||||
);
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||
ServerWorld overworld;
|
||||
WorldChunk chunk;
|
||||
|
||||
if (firstLaunch) {
|
||||
LOGGER.info("First launch, setting up");
|
||||
overworld = server.getOverworld();
|
||||
chunk = overworld.getChunk(0, 0);
|
||||
|
||||
if (firstLaunch) {
|
||||
LOGGER.info("First launch, testing attachment by feature");
|
||||
|
||||
if (featurePlaced) {
|
||||
if (!"feature".equals(chunk.getAttached(FEATURE_ATTACHMENT))) {
|
||||
throw new AssertionError("Feature did not write attachment to ProtoChunk");
|
||||
}
|
||||
} else {
|
||||
LOGGER.warn("Feature not placed, could not test writing during worldgen");
|
||||
}
|
||||
|
||||
LOGGER.info("setting up persistent attachments");
|
||||
|
||||
overworld = server.getOverworld();
|
||||
overworld.setAttached(PERSISTENT, "world_data");
|
||||
|
||||
chunk = overworld.getChunk(0, 0);
|
||||
chunk.setAttached(PERSISTENT, "chunk_data");
|
||||
|
||||
ProtoChunk protoChunk = (ProtoChunk) overworld.getChunkManager().getChunk(FAR_CHUNK_POS.x, FAR_CHUNK_POS.z, ChunkStatus.STRUCTURE_STARTS, true);
|
||||
protoChunk.setAttached(PERSISTENT, "protochunk_data");
|
||||
} else {
|
||||
LOGGER.info("Second launch, testing");
|
||||
LOGGER.info("Second launch, testing persistent attachments");
|
||||
|
||||
overworld = server.getOverworld();
|
||||
if (!"world_data".equals(overworld.getAttached(PERSISTENT))) throw new AssertionError();
|
||||
if (!"world_data".equals(overworld.getAttached(PERSISTENT))) throw new AssertionError("World attachement did not persist");
|
||||
if (!"chunk_data".equals(chunk.getAttached(PERSISTENT))) throw new AssertionError("WorldChunk attachement did not persist");
|
||||
|
||||
chunk = overworld.getChunk(0, 0);
|
||||
if (!"chunk_data".equals(chunk.getAttached(PERSISTENT))) throw new AssertionError();
|
||||
WrapperProtoChunk wrapperProtoChunk = (WrapperProtoChunk) overworld.getChunkManager().getChunk(0, 0, ChunkStatus.EMPTY, true);
|
||||
if (!"chunk_data".equals(wrapperProtoChunk.getAttached(PERSISTENT))) throw new AssertionError("Attachement is not accessible through WrapperProtoChunk");
|
||||
|
||||
Chunk farChunk = overworld.getChunkManager().getChunk(FAR_CHUNK_POS.x, FAR_CHUNK_POS.z, ChunkStatus.EMPTY, true);
|
||||
|
||||
if (farChunk instanceof WrapperProtoChunk) {
|
||||
LOGGER.warn("Far chunk alread generated, can't test persistence in ProtoChunk.");
|
||||
} else {
|
||||
if (!"protochunk_data".equals(farChunk.getAttached(PERSISTENT))) throw new AssertionError("ProtoChunk attachement did not persist");
|
||||
}
|
||||
}
|
||||
});
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register(server -> firstLaunch = false);
|
||||
|
||||
// Testing hint: load far chunk by running /tp @s 480 ~ 0
|
||||
ServerChunkEvents.CHUNK_LOAD.register(((world, chunk) -> {
|
||||
if (!chunk.getPos().equals(FAR_CHUNK_POS)) return;
|
||||
|
||||
LOGGER.info("Loaded chunk {}, testing transfer of attachments to WorldChunk", FAR_CHUNK_POS);
|
||||
|
||||
if (!"protochunk_data".equals(chunk.getAttached(PERSISTENT))) throw new AssertionError("ProtoChunk attachement was not transfered to WorldChunk");
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.test.attachment;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ProtoChunk;
|
||||
import net.minecraft.world.chunk.WrapperProtoChunk;
|
||||
import net.minecraft.world.gen.feature.DefaultFeatureConfig;
|
||||
import net.minecraft.world.gen.feature.Feature;
|
||||
import net.minecraft.world.gen.feature.util.FeatureContext;
|
||||
|
||||
public class SetAttachmentFeature extends Feature<DefaultFeatureConfig> {
|
||||
public SetAttachmentFeature(Codec<DefaultFeatureConfig> codec) {
|
||||
super(codec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generate(FeatureContext<DefaultFeatureConfig> context) {
|
||||
Chunk chunk = context.getWorld().getChunk(context.getOrigin());
|
||||
|
||||
if (chunk.getPos().equals(new ChunkPos(0, 0))) {
|
||||
AttachmentTestMod.featurePlaced = true;
|
||||
|
||||
if (!(chunk instanceof ProtoChunk) || chunk instanceof WrapperProtoChunk) {
|
||||
AttachmentTestMod.LOGGER.warn("Feature not attaching to ProtoChunk");
|
||||
}
|
||||
|
||||
chunk.setAttached(AttachmentTestMod.FEATURE_ATTACHMENT, "feature");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"feature": {
|
||||
"type": "fabric-data-attachment-api-v1-testmod:set_attachment",
|
||||
"config": {}
|
||||
},
|
||||
"placement": []
|
||||
}
|
|
@ -7,7 +7,8 @@
|
|||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabric-data-attachment-api-v1": "*",
|
||||
"fabric-lifecycle-events-v1": "*"
|
||||
"fabric-lifecycle-events-v1": "*",
|
||||
"fabric-biome-api-v1": "*"
|
||||
},
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
|
|
Loading…
Reference in a new issue