Skip to content

Commit

Permalink
Port more...
Browse files Browse the repository at this point in the history
Signed-off-by: Hei Piao <[email protected]>
  • Loading branch information
heipiao233 committed Aug 29, 2024
1 parent b86e963 commit bc61216
Show file tree
Hide file tree
Showing 565 changed files with 35,117 additions and 799 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ classes/
.vscode/
bin/
.classpath
.factorypath
.project

# macos
Expand All @@ -38,3 +39,5 @@ hs_err_*.log
replay_*.log
*.hprof
*.jfr

patches
14 changes: 14 additions & 0 deletions shearapi-attachment/build.gradle
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}"
}
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);
}
}
}
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() {}
}
Loading

0 comments on commit bc61216

Please sign in to comment.