generated from FabricMC/fabric-example-mod
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Hei Piao <[email protected]>
- Loading branch information
1 parent
b86e963
commit bc61216
Showing
565 changed files
with
35,117 additions
and
799 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
repositories { | ||
maven { | ||
url "https://jitpack.io/" | ||
name "Jitpack" | ||
} | ||
} | ||
|
||
dependencies { | ||
api project(path: ":shearapi-registries", configuration: "namedElements") | ||
api project(path: ":shearapi-utils", configuration: "namedElements") | ||
api project(path: ":shearapi-runtime", configuration: "namedElements") | ||
api project(path: ":shearapi-entity", configuration: "namedElements") | ||
modImplementation "com.github.Chocohead:Fabric-ASM:v${project.fabric_asm_version}" | ||
} |
224 changes: 224 additions & 0 deletions
224
shearapi-attachment/src/main/java/net/neoforged/neoforge/attachment/AttachmentHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
/* | ||
* Copyright (c) NeoForged and contributors | ||
* SPDX-License-Identifier: LGPL-2.1-only | ||
*/ | ||
|
||
package net.neoforged.neoforge.attachment; | ||
|
||
import com.mojang.logging.LogUtils; | ||
import java.util.IdentityHashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import net.minecraft.nbt.CompoundTag; | ||
import net.minecraft.nbt.Tag; | ||
import net.minecraft.resources.ResourceLocation; | ||
import net.pillowmc.shearapi.attachment.IntoAttachmentHolder; | ||
import net.pillowmc.shearapi.attachment.ShearAPIAttachmentInit; | ||
import net.pillowmc.shearapi.runtime.ShearAPIRuntime; | ||
|
||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.MustBeInvokedByOverriders; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.slf4j.Logger; | ||
|
||
/** | ||
* Implementation class for objects that can hold data attachments. | ||
* For the user-facing methods, see {@link IAttachmentHolder}. | ||
*/ | ||
public abstract class AttachmentHolder implements IAttachmentHolder, IntoAttachmentHolder { | ||
public static final String ATTACHMENTS_NBT_KEY = "neoforge:attachments"; | ||
private static final boolean IN_DEV = !ShearAPIRuntime.getRuntime().isProduction(); | ||
private static final Logger LOGGER = LogUtils.getLogger(); | ||
|
||
private void validateAttachmentType(AttachmentType<?> type) { | ||
Objects.requireNonNull(type); | ||
if (!IN_DEV) return; | ||
|
||
if (!ShearAPIAttachmentInit.ATTACHMENT_TYPES.containsValue(type)) { | ||
throw new IllegalArgumentException("Data attachment type with default value " + type.defaultValueSupplier.apply(getExposedHolder()) + " must be registered!"); | ||
} | ||
} | ||
|
||
@Nullable | ||
Map<AttachmentType<?>, Object> attachments = null; | ||
|
||
/** | ||
* Create the attachment map if it does not yet exist, or return the current map. | ||
*/ | ||
final Map<AttachmentType<?>, Object> getAttachmentMap() { | ||
if (attachments == null) { | ||
attachments = new IdentityHashMap<>(4); | ||
} | ||
return attachments; | ||
} | ||
|
||
/** | ||
* Returns the attachment holder that is exposed to the user. | ||
* This is the same as {@code this} for most cases, | ||
* but when using {@link AsField} it is the field holder. | ||
*/ | ||
IAttachmentHolder getExposedHolder() { | ||
return this; | ||
} | ||
|
||
@Override | ||
public final boolean hasAttachments() { | ||
return attachments != null && !attachments.isEmpty(); | ||
} | ||
|
||
@Override | ||
public final boolean hasData(AttachmentType<?> type) { | ||
validateAttachmentType(type); | ||
return attachments != null && attachments.containsKey(type); | ||
} | ||
|
||
@Override | ||
public final <T> T getData(AttachmentType<T> type) { | ||
validateAttachmentType(type); | ||
T ret = (T) getAttachmentMap().get(type); | ||
if (ret == null) { | ||
ret = type.defaultValueSupplier.apply(getExposedHolder()); | ||
attachments.put(type, ret); | ||
} | ||
return ret; | ||
} | ||
|
||
@Override | ||
public <T> Optional<T> getExistingData(AttachmentType<T> type) { | ||
validateAttachmentType(type); | ||
if (attachments == null) { | ||
return Optional.empty(); | ||
} | ||
return Optional.ofNullable((T) this.attachments.get(type)); | ||
} | ||
|
||
@Override | ||
@MustBeInvokedByOverriders | ||
public <T> @Nullable T setData(AttachmentType<T> type, T data) { | ||
validateAttachmentType(type); | ||
Objects.requireNonNull(data); | ||
return (T) getAttachmentMap().put(type, data); | ||
} | ||
|
||
@Override | ||
@MustBeInvokedByOverriders | ||
public <T> @Nullable T removeData(AttachmentType<T> type) { | ||
validateAttachmentType(type); | ||
if (attachments == null) { | ||
return null; | ||
} | ||
return (T) attachments.remove(type); | ||
} | ||
|
||
/** | ||
* Writes the serializable attachments to a tag. | ||
* Returns {@code null} if there are no serializable attachments. | ||
*/ | ||
@Nullable | ||
public final CompoundTag serializeAttachments() { | ||
if (attachments == null) { | ||
return null; | ||
} | ||
CompoundTag tag = null; | ||
for (var entry : attachments.entrySet()) { | ||
var type = entry.getKey(); | ||
if (type.serializer != null) { | ||
Tag serialized = ((IAttachmentSerializer<?, Object>) type.serializer).write(entry.getValue()); | ||
if (serialized != null) { | ||
if (tag == null) | ||
tag = new CompoundTag(); | ||
tag.put(ShearAPIAttachmentInit.ATTACHMENT_TYPES.getKey(type).toString(), serialized); | ||
} | ||
} | ||
} | ||
return tag; | ||
} | ||
|
||
/** | ||
* Reads serializable attachments from a tag previously created via {@link #serializeAttachments()}. | ||
*/ | ||
@ApiStatus.Internal | ||
public final void deserializeAttachments(CompoundTag tag) { | ||
for (var key : tag.getAllKeys()) { | ||
// Use tryParse to not discard valid attachment type keys, even if there is a malformed key. | ||
ResourceLocation keyLocation = ResourceLocation.tryParse(key); | ||
if (keyLocation == null) { | ||
LOGGER.error("Encountered invalid data attachment key {}. Skipping.", key); | ||
continue; | ||
} | ||
|
||
var type = ShearAPIAttachmentInit.ATTACHMENT_TYPES.get(keyLocation); | ||
if (type == null || type.serializer == null) { | ||
LOGGER.error("Encountered unknown or non-serializable data attachment {}. Skipping.", key); | ||
continue; | ||
} | ||
|
||
try { | ||
getAttachmentMap().put(type, ((IAttachmentSerializer<Tag, ?>) type.serializer).read(getExposedHolder(), tag.get(key))); | ||
} catch (Exception exception) { | ||
LOGGER.error("Failed to deserialize data attachment {}. Skipping.", key, exception); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Checks if two attachment holders have compatible attachments, | ||
* i.e. if they have the same serialized form. | ||
* | ||
* <p>Same as calling {@code Objects.equals(first.serializeAttachments(), second.serializeAttachments())}, | ||
* but implemented more efficiently. | ||
* | ||
* @return {@code true} if the attachments are compatible, {@code false} otherwise | ||
*/ | ||
public static <H extends AttachmentHolder> boolean areAttachmentsCompatible(H first, H second) { | ||
Map<AttachmentType<?>, Object> firstAttachments = first.attachments != null ? first.attachments : Map.of(); | ||
Map<AttachmentType<?>, Object> secondAttachments = second.attachments != null ? second.attachments : Map.of(); | ||
|
||
for (var entry : firstAttachments.entrySet()) { | ||
AttachmentType<Object> type = (AttachmentType<Object>) entry.getKey(); | ||
if (type.serializer != null) { | ||
var otherData = secondAttachments.get(type); | ||
if (otherData == null) | ||
// TODO: cache serialization of default value? | ||
otherData = type.defaultValueSupplier.apply(second.getExposedHolder()); | ||
if (!type.comparator.areCompatible(entry.getValue(), otherData)) | ||
return false; | ||
} | ||
} | ||
for (var entry : secondAttachments.entrySet()) { | ||
AttachmentType<Object> type = (AttachmentType<Object>) entry.getKey(); | ||
if (type.serializer != null) { | ||
var data = firstAttachments.get(type); | ||
if (data != null) | ||
continue; // already checked in the first loop | ||
data = type.defaultValueSupplier.apply(first.getExposedHolder()); | ||
if (!type.comparator.areCompatible(entry.getValue(), data)) | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Version of the {@link AttachmentHolder} that is suitable for storing in a field. | ||
* To be used when extending {@link AttachmentHolder} is not possible, | ||
* for example because the class already has a supertype. | ||
*/ | ||
public static class AsField extends AttachmentHolder { | ||
private final IAttachmentHolder exposedHolder; | ||
|
||
public AsField(IAttachmentHolder exposedHolder) { | ||
this.exposedHolder = exposedHolder; | ||
} | ||
|
||
@Override | ||
IAttachmentHolder getExposedHolder() { | ||
return exposedHolder; | ||
} | ||
|
||
public void deserializeInternal(CompoundTag tag) { | ||
deserializeAttachments(tag); | ||
} | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
shearapi-attachment/src/main/java/net/neoforged/neoforge/attachment/AttachmentInternals.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* Copyright (c) NeoForged and contributors | ||
* SPDX-License-Identifier: LGPL-2.1-only | ||
*/ | ||
|
||
package net.neoforged.neoforge.attachment; | ||
|
||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; | ||
import net.minecraft.nbt.CompoundTag; | ||
import net.minecraft.nbt.Tag; | ||
import net.minecraft.server.level.ServerPlayer; | ||
import net.minecraft.world.item.Item; | ||
import net.minecraft.world.item.ItemStack; | ||
import net.neoforged.bus.api.SubscribeEvent; | ||
import net.neoforged.neoforge.event.entity.living.LivingConversionEvent; | ||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
@ApiStatus.Internal | ||
public final class AttachmentInternals { | ||
/** | ||
* Marks a stack that has attachments and an empty NBT tag ({}). | ||
* If this marker is absent, we set the tag back to null after reading the attachments. | ||
*/ | ||
private static final String EMPTY_TAG_KEY = "neoforge:empty"; | ||
|
||
@Nullable | ||
public static CompoundTag addAttachmentsToTag(@Nullable CompoundTag tag, ItemStack stack, boolean fullCopy) { | ||
// Store all serializable attachments as an nbt subtag | ||
CompoundTag attachmentsTag = stack.getAttachmentHolder().serializeAttachments(); | ||
if (attachmentsTag != null) { | ||
if (tag == null) | ||
tag = new CompoundTag(); | ||
else { | ||
tag = tag.copy(); | ||
if (tag.isEmpty()) // Make sure we can differentiate between null and empty. | ||
tag.putBoolean(EMPTY_TAG_KEY, true); | ||
} | ||
tag.put(AttachmentHolder.ATTACHMENTS_NBT_KEY, attachmentsTag); | ||
} else if (fullCopy && tag != null) | ||
tag = tag.copy(); | ||
return tag; | ||
} | ||
|
||
/** | ||
* Perform the inverse operation of {@link #addAttachmentsToTag} on stack reception. | ||
*/ | ||
public static ItemStack reconstructItemStack(Item item, int count, @Nullable CompoundTag tag) { | ||
ItemStack itemstack; | ||
if (tag != null && tag.contains(AttachmentHolder.ATTACHMENTS_NBT_KEY, Tag.TAG_COMPOUND)) { | ||
// Read serialized caps | ||
itemstack = new ItemStack(item, count); | ||
itemstack.getAttachmentHolder().deserializeAttachments(tag.getCompound(AttachmentHolder.ATTACHMENTS_NBT_KEY)); | ||
tag = cleanTag(tag); | ||
} else { | ||
itemstack = new ItemStack(item, count); | ||
} | ||
itemstack.setTag(tag); | ||
return itemstack; | ||
} | ||
|
||
/** | ||
* Clean tag of its contained attachments (as set by {@link #addAttachmentsToTag}). | ||
*/ | ||
@Nullable | ||
public static CompoundTag cleanTag(CompoundTag tag) { | ||
tag.remove(AttachmentHolder.ATTACHMENTS_NBT_KEY); | ||
// If the tag is now empty and the empty marker is absent, replace by null. | ||
if (tag.contains(EMPTY_TAG_KEY)) | ||
tag.remove(EMPTY_TAG_KEY); | ||
else if (tag.isEmpty()) | ||
tag = null; | ||
return tag; | ||
} | ||
|
||
public static void onPlayerClone(ServerPlayer oldPlayer, ServerPlayer newPlayer, boolean alive) { | ||
AttachmentUtils.copyAttachments(oldPlayer, newPlayer, alive ? type -> true : type -> type.copyOnDeath); | ||
} | ||
|
||
@SubscribeEvent | ||
public static void onLivingConvert(LivingConversionEvent.Post event) { | ||
AttachmentUtils.copyAttachments(event.getEntity(), event.getOutcome(), type -> type.copyOnDeath); | ||
} | ||
|
||
static { | ||
ServerPlayerEvents.COPY_FROM.register(AttachmentInternals::onPlayerClone); | ||
} | ||
|
||
private AttachmentInternals() {} | ||
} |
Oops, something went wrong.