mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 11:20:55 -04:00
more advanced copy mechanics
- attachments require an EntityCopyHandler to be copied across entities - a copy handler is automatically derived if there's a codec - updated javadoc for chunk and BE targets
This commit is contained in:
parent
40ba2a4b44
commit
3f53b554fb
10 changed files with 233 additions and 63 deletions
fabric-data-attachment-api-v1/src
main/java/net/fabricmc/fabric
api/attachment/v1
impl/attachment
AttachmentEntrypoint.javaAttachmentRegistryImpl.javaAttachmentSerializingImpl.javaAttachmentTargetImpl.javaAttachmentTypeImpl.java
mixin/attachment
test/java/net/fabricmc/fabric/test/attachment
|
@ -102,24 +102,90 @@ public final class AttachmentRegistry {
|
|||
/**
|
||||
* A builder for creating {@link AttachmentType}s with finer control over their properties.
|
||||
*
|
||||
* <p>Note on entity attachments: sometimes, the game needs to copy data between two different entity instances.
|
||||
* This happens for example on player respawn, when a mob converts to another type, or when the player returns from the End.
|
||||
* Since one entity instance is discarded, it is imperative that the attached data doesn't hold a reference to the old instance.</p>
|
||||
* <ul>
|
||||
* <li>If a {@link Codec codec} is provided using {@link #codec(Codec)}, it will automatically be used for copying data.</li>
|
||||
* <li>If finer control is desired, a {@link AttachmentType.EntityCopyHandler custom copy handler} can be specified using {@link #entityCopyHandler(AttachmentType.EntityCopyHandler)}.</li>
|
||||
* <li>If neither are provided, <i>no attempt at copy will be made</i>, and attached data <b>will be lost</b>
|
||||
* in the situations outlined above. This can sometimes be useful for even finer control over copying, but
|
||||
* is generally undesirable for attachment types used on entities.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <A> the type of the attached data
|
||||
* @see #copyOnDeath()
|
||||
* @see #copyOnDeath(Codec)
|
||||
* @see #entityCopyHandler(AttachmentType.EntityCopyHandler)
|
||||
*/
|
||||
public interface Builder<A> {
|
||||
/**
|
||||
* Declares that attachments should persist between server restarts, using the provided {@link Codec} for
|
||||
* (de)serialization.
|
||||
* Declares that attachments corresponding to this type should persist between server restarts,
|
||||
* using the provided {@link Codec} for (de)serialization.
|
||||
*
|
||||
* <p>A shorthand for {@code persistent().codec(codec)}, cannot be used in conjunction with {@link #copyOnDeath(Codec)},
|
||||
* as {@link #codec(Codec)} can only be called once.</p>
|
||||
*
|
||||
* @param codec the codec used for (de)serialization
|
||||
* @return the builder
|
||||
* @see #codec(Codec)
|
||||
*/
|
||||
default Builder<A> persistent(Codec<A> codec) {
|
||||
return persistent().codec(codec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares that attachments corresponding to this type should persist between server restarts. A codec must be
|
||||
* declared using {@link #codec(Codec)} at some point, or {@link #buildAndRegister(Identifier)} will fail.
|
||||
*
|
||||
* @return the builder
|
||||
*/
|
||||
Builder<A> persistent();
|
||||
|
||||
/**
|
||||
* Declares that when an entity dies and respawns in some way, the attachments corresponding to this type
|
||||
* should be copied, using the provided {@link Codec} to copy data
|
||||
* between entity instances. This is used either when a player dies and respawns, or when a mob converts to another
|
||||
* (for example, zombie → drowned, or zombie villager → villager).
|
||||
*
|
||||
* <p>A shorthand for {@code copyEntityAttachments().codec(codec)}, and cannot be used in conjunction with {@link #persistent(Codec)},
|
||||
* as {@link #codec(Codec)} can only be called once.</p>
|
||||
*
|
||||
* @param codec a codec
|
||||
* @return the builder
|
||||
* @see #codec(Codec)
|
||||
*/
|
||||
default Builder<A> copyOnDeath(Codec<A> codec) {
|
||||
return copyOnDeath().codec(codec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares that when a player dies and respawns, the attachments corresponding to this type should remain.
|
||||
* When using this method, some method for attachment copying must be specified as explained in the description
|
||||
* of {@link Builder}, otherwise {@link #buildAndRegister(Identifier)} will fail.
|
||||
*
|
||||
* @return the builder
|
||||
* @see Builder
|
||||
*/
|
||||
Builder<A> copyOnDeath();
|
||||
|
||||
/**
|
||||
* Sets the {@link AttachmentType.EntityCopyHandler} for this attachment type, used when copying attachments between
|
||||
* entity instances.
|
||||
*
|
||||
* @param copyHandler the copy handler
|
||||
* @return the builder
|
||||
*/
|
||||
Builder<A> entityCopyHandler(AttachmentType.EntityCopyHandler<A> copyHandler);
|
||||
|
||||
/**
|
||||
* Sets the codec used for (de)serialization of this attachment type. Must only be called once during the
|
||||
* builder's existence.
|
||||
*
|
||||
* @param codec the codec used for (de)serialization
|
||||
* @return the builder
|
||||
*/
|
||||
Builder<A> persistent(Codec<A> codec);
|
||||
|
||||
/**
|
||||
* Declares that when a player dies and respawns, the attachments corresponding of this type should remain.
|
||||
*
|
||||
* @return the builder
|
||||
*/
|
||||
Builder<A> copyOnPlayerRespawn();
|
||||
Builder<A> codec(Codec<A> codec);
|
||||
|
||||
/**
|
||||
* Sets the default initializer for this attachment type. The initializer will be called by
|
||||
|
|
|
@ -26,12 +26,26 @@ import org.jetbrains.annotations.Nullable;
|
|||
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;
|
||||
|
||||
/**
|
||||
* 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>Note about {@link BlockEntity} and {@link WorldChunk} 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:
|
||||
* <pre>{@code
|
||||
* AttachmentType<MutableInt> MUTABLE_ATTACHMENT_TYPE = ...;
|
||||
* BlockEntity be = ...;
|
||||
* MutableInt data = be.getAttachedOrCreate(MUTABLE_ATTACHMENT_TYPE);
|
||||
* data.setValue(10);
|
||||
* be.markDirty(); // Required because we are not using setAttached
|
||||
* }</pre>
|
||||
* </p>
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@ApiStatus.NonExtendable
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.mojang.serialization.Codec;
|
|||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
/**
|
||||
|
@ -41,19 +42,17 @@ public interface AttachmentType<A> {
|
|||
Identifier identifier();
|
||||
|
||||
/**
|
||||
* An optional {@link Codec} used for reading and writing attachments to NBT for persistence.
|
||||
* An optional {@link Codec} used for reading and writing attachments to NBT for persistence and copying.
|
||||
*
|
||||
* @return the persistence codec, may be null
|
||||
* @return the persistence codec, may be {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
Codec<A> persistenceCodec();
|
||||
Codec<A> codec();
|
||||
|
||||
/**
|
||||
* @return whether the attachments persist across server restarts
|
||||
*/
|
||||
default boolean isPersistent() {
|
||||
return persistenceCodec() != null;
|
||||
}
|
||||
boolean persistent();
|
||||
|
||||
/**
|
||||
* If an object has no value associated to an attachment,
|
||||
|
@ -66,13 +65,40 @@ public interface AttachmentType<A> {
|
|||
* As an example, for a (mutable) list/array attachment type,
|
||||
* the initializer should create a new independent instance each time it is called.</p>
|
||||
*
|
||||
* @return the initializer for this attachment
|
||||
* @return the initializer for this attachment, may be {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
Supplier<A> initializer();
|
||||
|
||||
/**
|
||||
* @return the {@link EntityCopyHandler} of this attachment type, may be {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
AttachmentType.EntityCopyHandler<A> entityCopyHandler();
|
||||
|
||||
/**
|
||||
* @return whether the attachments should persist after a player's death and respawn
|
||||
*/
|
||||
boolean copyOnPlayerRespawn();
|
||||
boolean copyOnDeath();
|
||||
|
||||
/**
|
||||
* A functional interface to handle copying attachment data from an old entity instance to a new one, for example
|
||||
* during player respawn, entity conversion, or when an entity is teleported between worlds.
|
||||
*
|
||||
* <p>It is <i>imperative</i> that the data attached to the new entity doesn't hold a reference to the old entity,
|
||||
* as that can and will break in unexpected ways.</p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface EntityCopyHandler<T> {
|
||||
/**
|
||||
* Copies an attachment's data from an old entity instance (which will be discarded) to a new one. The data returned
|
||||
* will be attached to the new entity and <i>must not</i> hold a reference to the old one.
|
||||
*
|
||||
* @param original the previously attached data
|
||||
* @param oldEntity the entity to copy the attachment from
|
||||
* @param newEntity the entity to copy the attachment to
|
||||
* @return the new data for this attachment. <i>Must not</i> hold a reference to {@code oldEntity}.
|
||||
*/
|
||||
T copyAttachment(T original, Entity oldEntity, Entity newEntity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
|||
public class AttachmentEntrypoint implements ModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
ServerPlayerEvents.COPY_FROM.register((oldPlayer, newPlayer, alive) ->
|
||||
AttachmentTargetImpl.copyOnRespawn((AttachmentTargetImpl) oldPlayer, (AttachmentTargetImpl) newPlayer, alive)
|
||||
);
|
||||
ServerPlayerEvents.COPY_FROM.register(AttachmentTargetImpl::copyEntityAttachments);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.dynamic.RuntimeOps;
|
||||
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry;
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
|
||||
|
@ -43,6 +44,14 @@ public final class AttachmentRegistryImpl {
|
|||
}
|
||||
}
|
||||
|
||||
private static <T> AttachmentType.EntityCopyHandler<T> copyHandlerFromCodec(Codec<T> codec) {
|
||||
return (original, oldEntity, newEntity) -> codec.encodeStart(RuntimeOps.INSTANCE, original)
|
||||
.flatMap(s -> codec.decode(RuntimeOps.INSTANCE, s))
|
||||
.result()
|
||||
.orElseThrow()
|
||||
.getFirst();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AttachmentType<?> get(Identifier id) {
|
||||
return attachmentRegistry.get(id);
|
||||
|
@ -56,20 +65,43 @@ public final class AttachmentRegistryImpl {
|
|||
@Nullable
|
||||
private Supplier<A> defaultInitializer = null;
|
||||
@Nullable
|
||||
private Codec<A> persistenceCodec = null;
|
||||
private boolean copyOnPlayerRespawn = false;
|
||||
private Codec<A> codec = null;
|
||||
@Nullable
|
||||
private AttachmentType.EntityCopyHandler<A> copyHandler = null;
|
||||
private boolean persistent = false;
|
||||
private boolean copyOnDeath = false;
|
||||
|
||||
@Override
|
||||
public AttachmentRegistry.Builder<A> persistent(Codec<A> codec) {
|
||||
Objects.requireNonNull(codec, "codec cannot be null");
|
||||
|
||||
this.persistenceCodec = codec;
|
||||
public AttachmentRegistry.Builder<A> persistent() {
|
||||
this.persistent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachmentRegistry.Builder<A> copyOnPlayerRespawn() {
|
||||
this.copyOnPlayerRespawn = true;
|
||||
public AttachmentRegistry.Builder<A> copyOnDeath() {
|
||||
this.copyOnDeath = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachmentRegistry.Builder<A> entityCopyHandler(AttachmentType.EntityCopyHandler<A> copyHandler) {
|
||||
Objects.requireNonNull(copyHandler, "entity copy handler cannot be null");
|
||||
|
||||
this.copyHandler = copyHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachmentRegistry.Builder<A> codec(Codec<A> codec) {
|
||||
Objects.requireNonNull(codec, "codec cannot be null");
|
||||
|
||||
if (this.codec != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"A codec was already set for this attachment type. Declare it once using the Builder#codec() method instead"
|
||||
);
|
||||
}
|
||||
|
||||
this.codec = codec;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -83,7 +115,19 @@ public final class AttachmentRegistryImpl {
|
|||
|
||||
@Override
|
||||
public AttachmentType<A> buildAndRegister(Identifier id) {
|
||||
var attachment = new AttachmentTypeImpl<>(id, defaultInitializer, persistenceCodec, copyOnPlayerRespawn);
|
||||
if (codec == null && persistent) {
|
||||
throw new IllegalArgumentException("Persistence was enabled, but no codec was provided");
|
||||
}
|
||||
|
||||
if (codec != null && copyHandler == null) {
|
||||
this.copyHandler = copyHandlerFromCodec(codec);
|
||||
}
|
||||
|
||||
if (copyOnDeath && copyHandler == null) {
|
||||
throw new IllegalArgumentException("Copy on death was enabled, but no way of copying attachments was provided");
|
||||
}
|
||||
|
||||
var attachment = new AttachmentTypeImpl<>(id, defaultInitializer, codec, copyHandler, persistent, copyOnDeath);
|
||||
register(id, attachment);
|
||||
return attachment;
|
||||
}
|
||||
|
|
|
@ -45,9 +45,10 @@ public class AttachmentSerializingImpl {
|
|||
|
||||
for (Map.Entry<AttachmentType<?>, ?> entry : attachments.entrySet()) {
|
||||
AttachmentType<?> type = entry.getKey();
|
||||
Codec<Object> codec = (Codec<Object>) type.persistenceCodec();
|
||||
|
||||
if (codec != null) {
|
||||
if (type.persistent()) {
|
||||
Codec<Object> codec = (Codec<Object>) type.codec();
|
||||
// non-nullity enforced by builder API
|
||||
codec.encodeStart(NbtOps.INSTANCE, entry.getValue())
|
||||
.get()
|
||||
.ifRight(partial -> {
|
||||
|
@ -75,10 +76,10 @@ public class AttachmentSerializingImpl {
|
|||
continue;
|
||||
}
|
||||
|
||||
Codec<?> codec = type.persistenceCodec();
|
||||
|
||||
if (codec != null) {
|
||||
codec.parse(NbtOps.INSTANCE, compound.get(key))
|
||||
if (type.persistent()) {
|
||||
// non-nullity enforced by builder API
|
||||
type.codec()
|
||||
.parse(NbtOps.INSTANCE, compound.get(key))
|
||||
.get()
|
||||
.ifRight(partial -> {
|
||||
LOGGER.warn("Couldn't deserialize attachment " + type.identifier() + ", skipping. Error:");
|
||||
|
@ -100,7 +101,7 @@ public class AttachmentSerializingImpl {
|
|||
}
|
||||
|
||||
for (AttachmentType<?> type : map.keySet()) {
|
||||
if (type.isPersistent()) {
|
||||
if (type.persistent()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package net.fabricmc.fabric.impl.attachment;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
|
||||
import net.fabricmc.fabric.api.attachment.v1.AttachmentTarget;
|
||||
|
@ -25,19 +26,20 @@ 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, return from the End, or entity conversion.
|
||||
* In the first case, only the attachments with {@link AttachmentType#copyOnPlayerRespawn()} will be transferred.
|
||||
* Copies entity attachments a new instance is created.
|
||||
* Is triggered on player respawn, entity conversion, or return from the End.
|
||||
* In the first two cases, only the attachments with {@link AttachmentType#copyOnDeath()} will be copied.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static void copyOnRespawn(AttachmentTargetImpl original, AttachmentTargetImpl target, boolean alive) {
|
||||
Map<AttachmentType<?>, ?> attachments = original.fabric_getAttachments();
|
||||
static void copyEntityAttachments(Entity original, Entity target, boolean alive) {
|
||||
Map<AttachmentType<?>, ?> attachments = ((AttachmentTargetImpl) original).fabric_getAttachments();
|
||||
|
||||
for (Map.Entry<AttachmentType<?>, ?> entry : attachments.entrySet()) {
|
||||
AttachmentType<Object> type = (AttachmentType<Object>) entry.getKey();
|
||||
AttachmentType.EntityCopyHandler<Object> copyHandler = type.entityCopyHandler();
|
||||
|
||||
if (alive || type.copyOnPlayerRespawn()) {
|
||||
target.setAttached(type, entry.getValue());
|
||||
if (copyHandler != null && (alive || type.copyOnDeath())) {
|
||||
target.setAttached(type, copyHandler.copyAttachment(entry.getValue(), original, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
|
|||
public record AttachmentTypeImpl<A>(
|
||||
Identifier identifier,
|
||||
@Nullable Supplier<A> initializer,
|
||||
@Nullable Codec<A> persistenceCodec,
|
||||
boolean copyOnPlayerRespawn
|
||||
) implements AttachmentType<A> { }
|
||||
@Nullable Codec<A> codec,
|
||||
@Nullable EntityCopyHandler<A> entityCopyHandler,
|
||||
boolean persistent,
|
||||
boolean copyOnDeath
|
||||
) implements AttachmentType<A> {
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.spongepowered.asm.mixin.injection.At;
|
|||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.mob.MobEntity;
|
||||
|
||||
|
@ -39,6 +40,6 @@ abstract class MobEntityMixin implements AttachmentTargetImpl {
|
|||
CallbackInfoReturnable<T> cir,
|
||||
@Local MobEntity converted
|
||||
) {
|
||||
AttachmentTargetImpl.copyOnRespawn(this, (AttachmentTargetImpl) converted, true);
|
||||
AttachmentTargetImpl.copyEntityAttachments((Entity) (Object) this, converted, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,26 +147,41 @@ public class CommonAttachmentTests {
|
|||
|
||||
@Test
|
||||
void testEntityCopy() {
|
||||
AttachmentType<Boolean> notCopiedOnRespawn = AttachmentRegistry.create(
|
||||
new Identifier(MOD_ID, "not_copied_on_respawn")
|
||||
AttachmentType<Boolean> notCopiedAtAll = AttachmentRegistry.create(
|
||||
new Identifier(MOD_ID, "not_copied_at_all")
|
||||
);
|
||||
AttachmentType<Boolean> copiedOnRespawn = AttachmentRegistry.<Boolean>builder()
|
||||
.copyOnPlayerRespawn()
|
||||
.buildAndRegister(new Identifier(MOD_ID, "copied_on_respawn"));
|
||||
AttachmentType<Boolean> copiedNotOnDeath = AttachmentRegistry.<Boolean>builder()
|
||||
.codec(Codec.BOOL)
|
||||
.buildAndRegister(new Identifier(MOD_ID, "copied_not_on_death"));
|
||||
AttachmentType<Boolean> copiedOnDeath = AttachmentRegistry.<Boolean>builder()
|
||||
.copyOnDeath(Codec.BOOL)
|
||||
.buildAndRegister(new Identifier(MOD_ID, "copied_on_death"));
|
||||
AttachmentType<Boolean> customCopy = AttachmentRegistry.<Boolean>builder()
|
||||
.copyOnDeath()
|
||||
.entityCopyHandler((original, oldEntity, newEntity) -> !original) // very bad, only for testing
|
||||
.buildAndRegister(new Identifier(MOD_ID, "custom_copy"));
|
||||
|
||||
Entity original = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
original.setAttached(notCopiedOnRespawn, true);
|
||||
original.setAttached(copiedOnRespawn, true);
|
||||
original.setAttached(notCopiedAtAll, true);
|
||||
original.setAttached(copiedNotOnDeath, true);
|
||||
original.setAttached(copiedOnDeath, true);
|
||||
original.setAttached(customCopy, true);
|
||||
|
||||
Entity respawnTarget = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
Entity nonRespawnTarget = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
Entity nonDeathTarget = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
AttachmentTargetImpl.copyEntityAttachments(original, nonDeathTarget, true);
|
||||
|
||||
AttachmentTargetImpl.copyOnRespawn((AttachmentTargetImpl) original, (AttachmentTargetImpl) respawnTarget, false);
|
||||
AttachmentTargetImpl.copyOnRespawn((AttachmentTargetImpl) original, (AttachmentTargetImpl) nonRespawnTarget, true);
|
||||
assertTrue(respawnTarget.hasAttached(copiedOnRespawn));
|
||||
assertFalse(respawnTarget.hasAttached(notCopiedOnRespawn));
|
||||
assertTrue(nonRespawnTarget.hasAttached(copiedOnRespawn));
|
||||
assertTrue(nonRespawnTarget.hasAttached(notCopiedOnRespawn));
|
||||
assertFalse(nonDeathTarget.hasAttached(notCopiedAtAll));
|
||||
assertEquals(true, nonDeathTarget.getAttached(copiedNotOnDeath));
|
||||
assertEquals(true, nonDeathTarget.getAttached(copiedOnDeath));
|
||||
assertEquals(false, nonDeathTarget.getAttached(customCopy));
|
||||
|
||||
Entity deathTarget = mock(Entity.class, CALLS_REAL_METHODS);
|
||||
AttachmentTargetImpl.copyEntityAttachments(nonDeathTarget, deathTarget, false);
|
||||
|
||||
assertFalse(deathTarget.hasAttached(notCopiedAtAll));
|
||||
assertFalse(deathTarget.hasAttached(copiedNotOnDeath));
|
||||
assertEquals(true, deathTarget.getAttached(copiedOnDeath));
|
||||
assertEquals(true, deathTarget.getAttached(customCopy));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue