Skip to content

Commit

Permalink
- keep lore tag as well
Browse files Browse the repository at this point in the history
- update to gradle 7
  • Loading branch information
Fourmisain committed Apr 17, 2021
1 parent f4657bf commit 7c072ca
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Keep Head Names

A Fabric Minecraft mod that makes it so that player heads keep their display names after being placed down and picked up again.<br>
A Fabric Minecraft mod that makes it so that player heads keep their display names and lore tags after being placed down and picked up again.<br>
It is an unofficial fix for [MC-174496](https://bugs.mojang.com/browse/MC-174496).

* requires Minecraft 1.16.2+, Fabric Loader 0.11.1+ and Fabric API
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version '0.6-SNAPSHOT'
id 'fabric-loom' version '0.7-SNAPSHOT'
id 'maven-publish'
}

Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
org.gradle.jvmargs=-Xmx1G

# Fabric Properties
# check these on https://modmuss50.me/fabric.html
# check these e.g. on https://spaceclouds42.github.io/fabric-versions
minecraft_version=1.16.5
yarn_mappings=1.16.5+build.6
loader_version=0.11.3
fabric_api_version=0.32.5+1.16
fabric_api_version=0.32.9+1.16

# Mod Properties
mod_version=1.1
mod_version=1.2
maven_group=fourmisain
archives_base_name=KeepHeadNames
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
1 change: 0 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
Expand Down
35 changes: 34 additions & 1 deletion src/main/java/fourmisain/keepheadnames/KeepHeadNames.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,62 @@
package fourmisain.keepheadnames;

import fourmisain.keepheadnames.mixin.LootFunctionTypesAccessor;
import fourmisain.keepheadnames.util.CopyLoreLootFunction;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.loot.v1.event.LootTableLoadingCallback;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.loot.ConstantLootTableRange;
import net.minecraft.loot.LootPool;
import net.minecraft.loot.LootTable;
import net.minecraft.loot.entry.ItemEntry;
import net.minecraft.loot.function.CopyNameLootFunction;
import net.minecraft.loot.function.CopyNbtLootFunction;
import net.minecraft.loot.function.LootFunctionType;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;

public class KeepHeadNames implements ModInitializer {
private static final Identifier PLAYER_HEAD_LOOT_TABLE_ID = new Identifier("blocks/player_head");

public static LootFunctionType COPY_LORE;

@Nullable
public static ListTag getLore(ItemStack itemStack) {
CompoundTag compoundTag = itemStack.getSubTag("display");

if (compoundTag != null && compoundTag.contains("Lore", 9)) {
return compoundTag.getList("Lore", 8);
}

return null;
}

public static void setLore(ItemStack itemStack, @Nullable ListTag lore) {
CompoundTag compoundTag = itemStack.getOrCreateSubTag("display");

if (lore != null) {
compoundTag.put("Lore", lore);
} else {
compoundTag.remove("Lore");
}
}

@Override
public void onInitialize() {
COPY_LORE = LootFunctionTypesAccessor.register("copy_lore", new CopyLoreLootFunction.Serializer());

LootTableLoadingCallback.EVENT.register((resourceManager, lootManager, id, supplier, setter) -> {
if (id.equals(PLAYER_HEAD_LOOT_TABLE_ID)) {
// ensure mined player heads keep their display tag
// ensure mined player heads keep their display tag and lore
setter.set(LootTable.builder()
.pool(LootPool.builder()
.rolls(ConstantLootTableRange.create(1))
.with(ItemEntry.builder(Items.PLAYER_HEAD)
.apply(CopyNameLootFunction.builder(CopyNameLootFunction.Source.BLOCK_ENTITY))
.apply(CopyLoreLootFunction.builder(CopyLoreLootFunction.Source.BLOCK_ENTITY))
.apply(CopyNbtLootFunction.builder(CopyNbtLootFunction.Source.BLOCK_ENTITY)
.withOperation("SkullOwner", "SkullOwner"))
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package fourmisain.keepheadnames.mixin;

import net.minecraft.loot.function.LootFunction;
import net.minecraft.loot.function.LootFunctionType;
import net.minecraft.loot.function.LootFunctionTypes;
import net.minecraft.util.JsonSerializer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(LootFunctionTypes.class)
public interface LootFunctionTypesAccessor {
@Invoker("register")
static LootFunctionType register(String id, JsonSerializer<? extends LootFunction> jsonSerializer) {
throw new AssertionError();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fourmisain.keepheadnames.mixin;

import fourmisain.keepheadnames.NameSettable;
import fourmisain.keepheadnames.util.Loreable;
import fourmisain.keepheadnames.util.NameSettable;
import net.minecraft.block.BlockState;
import net.minecraft.block.PlayerSkullBlock;
import net.minecraft.block.entity.BlockEntity;
Expand All @@ -13,16 +14,18 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/** Stores the display name tag from the ItemStack inside the placed SkullBlockEntity */
import java.util.Objects;

import static fourmisain.keepheadnames.KeepHeadNames.getLore;

/** Stores the display name and lore tag from the ItemStack inside the placed SkullBlockEntity */
@Mixin(PlayerSkullBlock.class)
public class PlayerSkullBlockMixin {

@Inject(at = @At("TAIL"), method = "onPlaced")
public void onPlaced(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack, CallbackInfo ci) {
BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof NameSettable) {
NameSettable nameSettable = (NameSettable)blockEntity;
nameSettable.setCustomName(itemStack.getName());
}
BlockEntity blockEntity = Objects.requireNonNull(world.getBlockEntity(pos));

((NameSettable) blockEntity).setCustomName(itemStack.getName());
((Loreable) blockEntity).setLore(getLore(itemStack));
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
package fourmisain.keepheadnames.mixin;

import fourmisain.keepheadnames.NameSettable;
import fourmisain.keepheadnames.util.Loreable;
import fourmisain.keepheadnames.util.NameSettable;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.SkullBlockEntity;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.Nameable;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.Implements;
import org.spongepowered.asm.mixin.Interface;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

/** Add Nameable interface to SkullBlockEntity and de/serialize the custom name */
/** Add Nameable interface to SkullBlockEntity and de/serialize the custom name and lore tags */
@Mixin(SkullBlockEntity.class)
@Implements({
@Interface(iface = Nameable.class, prefix = "nameable$"),
@Interface(iface = NameSettable.class, prefix = "namesettable$", remap = Interface.Remap.NONE, unique = true)
@Interface(iface = NameSettable.class, prefix = "namesettable$", remap = Interface.Remap.NONE, unique = true),
@Interface(iface = Loreable.class, prefix = "loreable$", remap = Interface.Remap.NONE, unique = true)
})
public abstract class SkullBlockEntityMixin implements Nameable, NameSettable {

@Unique
Text customName;
public abstract class SkullBlockEntityMixin implements Nameable {
@Unique Text customName;
@Unique ListTag lore;

public Text nameable$getName() {
if (customName != null)
Expand All @@ -41,19 +46,35 @@ public abstract class SkullBlockEntityMixin implements Nameable, NameSettable {
this.customName = customName;
}

/** Serializes the stored display name */
public ListTag loreable$getLore() {
return lore;
}

public void loreable$setLore(ListTag lore) {
this.lore = lore;
}

/** Serializes the stored display name and lore */
@Inject(at = @At("RETURN"), method = "toTag")
public void toTag(CompoundTag tag, CallbackInfoReturnable<CompoundTag> cir) {
if (customName != null) {
tag.putString("CustomName", Text.Serializer.toJson(customName));
}

if (lore != null) {
tag.put("Lore", lore);
}
}

/** Deserializes the stored display name */
/** Deserializes the stored display name and lore */
@Inject(at = @At("HEAD"), method = "fromTag")
public void fromTag(BlockState state, CompoundTag tag, CallbackInfo ci) {
if (tag.contains("CustomName", 8)) {
customName = Text.Serializer.fromJson(tag.getString("CustomName"));
}

if (tag.contains("Lore", 9)) {
lore = tag.getList("Lore", 8);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package fourmisain.keepheadnames.util;

import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import fourmisain.keepheadnames.KeepHeadNames;
import net.minecraft.item.ItemStack;
import net.minecraft.loot.condition.LootCondition;
import net.minecraft.loot.context.LootContext;
import net.minecraft.loot.context.LootContextParameter;
import net.minecraft.loot.context.LootContextParameters;
import net.minecraft.loot.function.ConditionalLootFunction;
import net.minecraft.loot.function.LootFunctionType;
import net.minecraft.util.JsonHelper;

import java.util.Set;

import static fourmisain.keepheadnames.KeepHeadNames.setLore;

public class CopyLoreLootFunction extends ConditionalLootFunction {
private final CopyLoreLootFunction.Source source;

private CopyLoreLootFunction(LootCondition[] conditions, CopyLoreLootFunction.Source source) {
super(conditions);
this.source = source;
}

public LootFunctionType getType() {
return KeepHeadNames.COPY_LORE;
}

public Set<LootContextParameter<?>> getRequiredParameters() {
return ImmutableSet.of(this.source.parameter);
}

public ItemStack process(ItemStack itemStack, LootContext context) {
Object object = context.get(this.source.parameter);

if (object instanceof Loreable) {
Loreable loreable = (Loreable)object;
setLore(itemStack, loreable.getLore());
}

return itemStack;
}

public static ConditionalLootFunction.Builder<?> builder(CopyLoreLootFunction.Source source) {
return builder((conditions) -> new CopyLoreLootFunction(conditions, source));
}

public static class Serializer extends ConditionalLootFunction.Serializer<CopyLoreLootFunction> {
public void toJson(JsonObject jsonObject, CopyLoreLootFunction copyNameLootFunction, JsonSerializationContext jsonSerializationContext) {
super.toJson(jsonObject, copyNameLootFunction, jsonSerializationContext);
jsonObject.addProperty("source", copyNameLootFunction.source.name);
}

public CopyLoreLootFunction fromJson(JsonObject jsonObject, JsonDeserializationContext jsonDeserializationContext, LootCondition[] lootConditions) {
CopyLoreLootFunction.Source source = CopyLoreLootFunction.Source.valueOf(JsonHelper.getString(jsonObject, "source"));
return new CopyLoreLootFunction(lootConditions, source);
}
}

public enum Source {
THIS("this", LootContextParameters.THIS_ENTITY),
KILLER("killer", LootContextParameters.KILLER_ENTITY),
KILLER_PLAYER("killer_player", LootContextParameters.LAST_DAMAGE_PLAYER),
BLOCK_ENTITY("block_entity", LootContextParameters.BLOCK_ENTITY);

public final String name;
public final LootContextParameter<?> parameter;

Source(String name, LootContextParameter<?> parameter) {
this.name = name;
this.parameter = parameter;
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/fourmisain/keepheadnames/util/Loreable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package fourmisain.keepheadnames.util;

import net.minecraft.nbt.ListTag;

public interface Loreable {
void setLore(ListTag lore);
ListTag getLore();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package fourmisain.keepheadnames;
package fourmisain.keepheadnames.util;

import net.minecraft.text.Text;

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/keepheadnames.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"package": "fourmisain.keepheadnames.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"LootFunctionTypesAccessor",
"PlayerSkullBlockMixin",
"SkullBlockEntityMixin"
],
Expand Down

0 comments on commit 7c072ca

Please sign in to comment.