Reworked durability configs to be dynamically set during inventory tick update whenever a config change is detected. Added unbreakable text to totem tooltip. Fixed server world crashing when reviving player due to closing level (try-with-resources).

This commit is contained in:
2026-01-18 00:00:36 +01:00
parent 5a9fd203d3
commit 7afc1956bb
10 changed files with 105 additions and 56 deletions

View File

@ -0,0 +1,46 @@
package dev.micle.totem_of_reviving.component;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import java.util.Objects;
public class TotemConfigHashData {
public static final Codec<TotemConfigHashData> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.INT.fieldOf("configHash").forGetter(TotemConfigHashData::getConfigHash)
).apply(instance, TotemConfigHashData::new)
);
public static final StreamCodec<ByteBuf, TotemConfigHashData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, TotemConfigHashData::getConfigHash,
TotemConfigHashData::new
);
private final int configHash;
public TotemConfigHashData(int configHash) {
this.configHash = configHash;
}
public int getConfigHash() {
return this.configHash;
}
@Override
public int hashCode() {
return Objects.hash(this.configHash);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else {
return obj instanceof TotemConfigHashData tchd
&& this.configHash == tchd.configHash;
}
}
}

View File

@ -7,7 +7,7 @@ import net.minecraft.world.item.Rarity;
public class DiamondTotemItem extends TotemItem {
public DiamondTotemItem() {
super(createProperties(Rarity.RARE, Config.Server.getDiamondTotemConfig().getDurability(), false));
super(createProperties(Rarity.RARE, false));
}
public static String getName() {

View File

@ -7,7 +7,7 @@ import net.minecraft.world.item.Rarity;
public class IronTotemItem extends TotemItem {
public IronTotemItem() {
super(createProperties(Rarity.COMMON, Config.Server.getIronTotemConfig().getDurability(), false));
super(createProperties(Rarity.COMMON, false));
}
public static String getName() {

View File

@ -7,7 +7,7 @@ import net.minecraft.world.item.Rarity;
public class NetheriteTotemItem extends TotemItem {
public NetheriteTotemItem() {
super(createProperties(Rarity.EPIC, Config.Server.getNetheriteTotemConfig().getDurability(), true));
super(createProperties(Rarity.EPIC, true));
}
public static String getName() {

View File

@ -7,7 +7,7 @@ import net.minecraft.world.item.Rarity;
public class StrawTotemItem extends TotemItem {
public StrawTotemItem() {
super(createProperties(Rarity.UNCOMMON, Config.Server.getStrawTotemConfig().getDurability(), false));
super(createProperties(Rarity.UNCOMMON, false));
}
public static String getName() {

View File

@ -1,6 +1,7 @@
package dev.micle.totem_of_reviving.item.totem;
import com.mojang.blaze3d.platform.InputConstants;
import dev.micle.totem_of_reviving.component.TotemConfigHashData;
import dev.micle.totem_of_reviving.component.TotemData;
import dev.micle.totem_of_reviving.setup.Config;
import dev.micle.totem_of_reviving.setup.ModDataComponents;
@ -8,6 +9,7 @@ import dev.micle.totem_of_reviving.util.ComponentHelper;
import dev.micle.totem_of_reviving.util.LangAsset;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
@ -16,17 +18,18 @@ import net.minecraft.server.players.PlayerList;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Rarity;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.Unbreakable;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ -36,8 +39,8 @@ public abstract class TotemItem extends Item {
super(properties);
}
public static Properties createProperties(Rarity rarity, int durability, boolean isFireResistant) {
Properties properties = new Properties().stacksTo(1).rarity(rarity).durability(durability);
public static Properties createProperties(Rarity rarity, boolean isFireResistant) {
Properties properties = new Properties().stacksTo(1).rarity(rarity);
if (isFireResistant) properties = properties.fireResistant();
@ -160,28 +163,18 @@ public abstract class TotemItem extends Item {
).withStyle(ChatFormatting.GRAY);
}
try (ServerLevel targetLevel = target.serverLevel()) {
if (!targetLevel.equals(user.serverLevel()) && !config.getCanReviveAcrossDimensions()) {
return LangAsset.MESSAGE_PLAYER_IS_IN_ANOTHER_DIMENSION.getComponent(
Component.literal(totemData.getTargetName()).withStyle(ChatFormatting.WHITE)
).withStyle(ChatFormatting.GRAY);
}
} catch (IOException e) {
return LangAsset.MESSAGE_UNABLE_TO_GET_PLAYER_WORLD.getComponent(
Component.literal(totemData.getTargetName()).withStyle(ChatFormatting.RED)
).withStyle(ChatFormatting.DARK_RED);
if (!target.serverLevel().equals(user.serverLevel()) && !config.getCanReviveAcrossDimensions()) {
return LangAsset.MESSAGE_PLAYER_IS_IN_ANOTHER_DIMENSION.getComponent(
Component.literal(totemData.getTargetName()).withStyle(ChatFormatting.WHITE)
).withStyle(ChatFormatting.GRAY);
}
if (!totemItem.canAffordTarget(totemData)) {
return LangAsset.MESSAGE_NOT_ENOUGH_CHARGE.getComponent().withStyle(ChatFormatting.WHITE);
}
try (ServerLevel userLevel = user.serverLevel()) {
target.teleportTo(userLevel, user.position().x, user.position().y, user.position().z, user.getYRot(), user.getXRot());
target.setGameMode(userLevel.getServer().getDefaultGameType());
} catch (IOException e) {
return LangAsset.MESSAGE_UNABLE_TO_GET_YOUR_WORLD.getComponent().withStyle(ChatFormatting.RED);
}
target.teleportTo(user.serverLevel(), user.position().x, user.position().y, user.position().z, user.getYRot(), user.getXRot());
target.setGameMode(user.serverLevel().getServer().getDefaultGameType());
totemData = new TotemData(
totemData.getTargetIndex(),
@ -234,6 +227,9 @@ public abstract class TotemItem extends Item {
if (getConfig().getCanReviveAcrossDimensions()) {
tooltipComponents.add(LangAsset.TOOLTIP_TOTEM_CAN_REVIVE_ACROSS_DIMENSIONS.getComponent().withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC));
}
if (!stack.isDamageableItem()) {
tooltipComponents.add(LangAsset.TOOLTIP_TOTEM_IS_UNBREAKABLE.getComponent().withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC));
}
tooltipComponents.add(Component.empty());
@ -256,6 +252,31 @@ public abstract class TotemItem extends Item {
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
@Override
@ParametersAreNonnullByDefault
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean isSelected) {
if (level.isClientSide) {
super.inventoryTick(stack, level, entity, slotId, isSelected);
return;
}
TotemConfigHashData totemConfigHashData = stack.get(ModDataComponents.TOTEM_CONFIG_HASH_DATA);
if (totemConfigHashData == null || totemConfigHashData.getConfigHash() != getConfig().getConfigHash()) {
if (getConfig().getDurability() == 0) {
stack.set(DataComponents.UNBREAKABLE, new Unbreakable(false));
stack.remove(DataComponents.MAX_DAMAGE);
stack.remove(DataComponents.DAMAGE);
} else {
stack.remove(DataComponents.UNBREAKABLE);
stack.set(DataComponents.MAX_DAMAGE, getConfig().getDurability());
stack.set(DataComponents.DAMAGE, 0);
}
stack.set(ModDataComponents.TOTEM_CONFIG_HASH_DATA, new TotemConfigHashData(getConfig().getConfigHash()));
}
super.inventoryTick(stack, level, entity, slotId, isSelected);
}
@Override
@ParametersAreNonnullByDefault
public @NotNull InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand usedHand) {
@ -283,26 +304,4 @@ public abstract class TotemItem extends Item {
return super.use(level, player, usedHand);
}
@Override
@ParametersAreNonnullByDefault
public int getMaxDamage(ItemStack stack) {
return getConfig().getDurability();
}
@Override
@ParametersAreNonnullByDefault
public boolean isDamageable(ItemStack stack) {
return getConfig().getDurability() != 0;
}
@Override
@ParametersAreNonnullByDefault
public int getDamage(ItemStack stack) {
if (isDamageable(stack) && super.getDamage(stack) >= getMaxDamage(stack)) {
stack.setCount(0);
return getMaxDamage(stack);
}
return (isDamageable(stack)) ? super.getDamage(stack) : 0;
}
}

View File

@ -8,6 +8,8 @@ import net.neoforged.fml.ModContainer;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.neoforge.common.ModConfigSpec;
import java.util.Objects;
public final class Config {
public static void init(ModContainer modContainer) {
modContainer.registerConfig(ModConfig.Type.SERVER, Server.SPEC);
@ -24,8 +26,6 @@ public final class Config {
static {
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
builder.comment("WHEN MAKING CHANGES IT IS RECOMMENDED TO NOT BE IN A WORLD.\n" +
"CHANGES WILL MOST LIKELY REQUIRE A RESTART FOR EVERYTHING TO WORK PROPERLY.");
STRAW_TOTEM_CONFIG = new TotemConfig(builder, StrawTotemItem.getName(), -1, 3,
1, false, false, 1);
IRON_TOTEM_CONFIG = new TotemConfig(builder, IronTotemItem.getName(), -1, 5,
@ -85,13 +85,7 @@ public final class Config {
public double getChargeCostMultiplier() { return CHARGE_COST_MULTIPLIER.get(); }
public boolean getCanReviveMoreExpensiveTargets() { return CAN_REVIVE_MORE_EXPENSIVE_TARGETS.get(); }
public boolean getCanReviveAcrossDimensions() { return CAN_REVIVE_ACROSS_DIMENSIONS.get(); }
public int getDurability() {
try {
return DURABILITY.get();
} catch (IllegalStateException e) {
return 10;
}
}
public int getDurability() { return DURABILITY.get(); }
public int getConfigHash() { return Objects.hash(getDurability()); }
}
}

View File

@ -1,16 +1,24 @@
package dev.micle.totem_of_reviving.setup;
import dev.micle.totem_of_reviving.component.TotemConfigHashData;
import dev.micle.totem_of_reviving.component.TotemData;
import net.minecraft.core.component.DataComponentType;
import net.neoforged.neoforge.registries.DeferredHolder;
public class ModDataComponents {
public static final DeferredHolder<DataComponentType<?>, DataComponentType<TotemData>> TOTEM_DATA = Registration.DATA_COMPONENTS.registerComponentType(
"basic",
"totem_data",
builder -> builder
.persistent(TotemData.CODEC)
.networkSynchronized(TotemData.STREAM_CODEC)
);
public static final DeferredHolder<DataComponentType<?>, DataComponentType<TotemConfigHashData>> TOTEM_CONFIG_HASH_DATA = Registration.DATA_COMPONENTS.registerComponentType(
"totem_config_hash_data",
builder -> builder
.persistent(TotemConfigHashData.CODEC)
.networkSynchronized(TotemConfigHashData.STREAM_CODEC)
);
public static void register() {}
}

View File

@ -21,6 +21,7 @@ public enum LangAsset {
TOOLTIP_TOTEM_DYNAMIC_COST("tooltip", "totem_dynamic_cost"),
TOOLTIP_TOTEM_CAN_REVIVE_MORE_EXPENSIVE_TARGETS("tooltip", "totem_can_revive_more_expensive_targets"),
TOOLTIP_TOTEM_CAN_REVIVE_ACROSS_DIMENSIONS("tooltip", "totem_can_revive_across_dimensions"),
TOOLTIP_TOTEM_IS_UNBREAKABLE("tooltip", "totem_is_unbreakable"),
TOOLTIP_TOTEM_REVIVE_INSTRUCTION("tooltip", "totem_revive_instruction"),
TOOLTIP_TOTEM_CHARGE_INSTRUCTION("tooltip", "totem_charge_instruction"),
TOOLTIP_TOTEM_CHANGE_TARGET_INSTRUCTION("tooltip", "totem_change_target_instructions"),

View File

@ -14,6 +14,7 @@
"tooltip.totem_of_reviving.totem_dynamic_cost": "Dynamic cost (Multiplier: %s).",
"tooltip.totem_of_reviving.totem_can_revive_more_expensive_targets": "Can revive more expensive targets.",
"tooltip.totem_of_reviving.totem_can_revive_across_dimensions": "Can revive across dimensions.",
"tooltip.totem_of_reviving.totem_is_unbreakable": "Unbreakable.",
"tooltip.totem_of_reviving.totem_revive_instruction": "When second hand is empty: revive target.",
"tooltip.totem_of_reviving.totem_charge_instruction": "When second hand has a reviving charge: charge totem.",
"tooltip.totem_of_reviving.totem_change_target_instructions": "Change target.",