diff --git a/build.gradle b/build.gradle
index a7f8f22..e09df1e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -119,8 +119,11 @@ configurations {
repositories {
maven { url "https://maven.createmod.net" } // Ponder, Flywheel
- maven { url = "https://maven.ithundxr.dev/snapshots" } // Registrate
+ maven { url "https://maven.ithundxr.dev/snapshots" } // Registrate
maven { url "https://maven.blamejared.com" } // JEI
+ maven {
+ url "https://api.modrinth.com/maven"
+ }
}
dependencies {
@@ -134,6 +137,8 @@ dependencies {
compileOnly("mezz.jei:jei-${minecraft_version}-neoforge-api:${jei_version}")
runtimeOnly("mezz.jei:jei-${minecraft_version}-neoforge:${jei_version}")
+
+ implementation("maven.modrinth:detail-armor-bar-reconstructed:${detail_armor_bar_version}")
}
// This block of code expands all declared replace properties in the specified resource targets.
diff --git a/gradle.properties b/gradle.properties
index efa8e9c..d30ca52 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -32,7 +32,7 @@ mod_name=Protection Engineering
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=MIT
# The mod version. See https://semver.org/
-mod_version=1.0.4
+mod_version=1.1.0
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
@@ -45,4 +45,5 @@ create_version_range=[6.0.8,)
ponder_version=1.0.82
flywheel_version=1.0.6
registrate_version=MC1.21-1.3.0+67
-jei_version=19.27.0.340
\ No newline at end of file
+jei_version=19.27.0.340
+detail_armor_bar_version=5.0.2
\ No newline at end of file
diff --git a/src/main/java/com/chemiofitor/protection_engineering/ProtectionEngineering.java b/src/main/java/com/chemiofitor/protection_engineering/ProtectionEngineering.java
index 1865767..81dd545 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/ProtectionEngineering.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/ProtectionEngineering.java
@@ -1,5 +1,6 @@
package com.chemiofitor.protection_engineering;
+import com.chemiofitor.protection_engineering.compat.DetailArmorBarCompat;
import com.chemiofitor.protection_engineering.config.PEConfig;
import com.chemiofitor.protection_engineering.config.PEServerConfig;
import com.chemiofitor.protection_engineering.data.PEDataGen;
@@ -12,11 +13,13 @@
import com.chemiofitor.protection_engineering.registry.PEWorkbench;
import com.mojang.logging.LogUtils;
import com.tterrag.registrate.Registrate;
+import net.neoforged.neoforge.common.NeoForge;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
+import net.neoforged.fml.ModList;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import org.slf4j.Logger;
@@ -56,6 +59,13 @@ public ProtectionEngineering(IEventBus modEventBus, ModContainer modContainer) {
modEventBus.addListener(EventPriority.HIGHEST, PEDataGen::gatherDataHighPriority);
modEventBus.addListener(EventPriority.LOWEST, PEDataGen::gatherData);
+
+ if (ModList.get().isLoaded("detailab")) {
+ modEventBus.addListener(DetailArmorBarCompat::onClientSetup);
+ }
+
+ NeoForge.EVENT_BUS.register(
+ new com.chemiofitor.protection_engineering.event.PEGameEvents.EquipmentHandler());
}
// ── Helpers ─────────────────────────────────────────────────
diff --git a/src/main/java/com/chemiofitor/protection_engineering/api/AttachmentsData.java b/src/main/java/com/chemiofitor/protection_engineering/api/AttachmentsData.java
index f4bcb13..e450eae 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/api/AttachmentsData.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/api/AttachmentsData.java
@@ -6,26 +6,44 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.*;
/**
- * 宿主物品上的附件数据。
- *
- * 用 {@link Map}{@code } 存储,只存非空值。
- * 不同宿主物品(头盔、胸甲、锯剑)各自只存自己支持的槽位子集,
- * 没有"定长 N"的限制。
- *
- * 不可变 —— {@link #with} / {@link #without} 返回新实例。
+ * 宿主物品上的附件数据。自定义 equals/hashCode 用 ItemStack.matches 做值比较。
*/
-public record AttachmentsData(Map slots) {
+public final class AttachmentsData {
+
+ private final Map slots;
public static final AttachmentsData EMPTY = new AttachmentsData(Map.of());
- /** 防御性拷贝,确保不可变 */
- public AttachmentsData {
- slots = Map.copyOf(slots);
+ public AttachmentsData(Map slots) {
+ this.slots = Map.copyOf(slots);
+ }
+
+ public Map slots() { return slots; }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AttachmentsData other)) return false;
+ if (slots.size() != other.slots.size()) return false;
+ for (var entry : slots.entrySet()) {
+ ItemStack otherStack = other.slots.get(entry.getKey());
+ if (otherStack == null) return false;
+ // 仅比较物品类型,忽略组件变化(防止状态更新触发装备音效)
+ if (!ItemStack.matches(entry.getValue(), otherStack)) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int h = 0;
+ for (var entry : slots.entrySet()) {
+ h += Objects.hash(entry.getKey(), ItemStack.hashItemAndComponents(entry.getValue()));
+ }
+ return h;
}
// ── Codec ──────────────────────────────────────────────────
diff --git a/src/main/java/com/chemiofitor/protection_engineering/block/WorkbenchBlock.java b/src/main/java/com/chemiofitor/protection_engineering/block/WorkbenchBlock.java
index 61daddb..9f33fc5 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/block/WorkbenchBlock.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/block/WorkbenchBlock.java
@@ -8,6 +8,7 @@
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
+import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -16,6 +17,9 @@
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
import javax.annotation.Nullable;
@@ -80,4 +84,12 @@ protected void onRemove(BlockState state, Level level, BlockPos pos,
super.onRemove(state, level, pos, newState, moved);
}
}
+
+ @Override
+ protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
+ return Shapes.or(
+ Shapes.box(0.0D / 16, 0.0D / 16, 0.0D / 16, 16.0D / 16, 9.0D / 16, 16.0D / 16),
+ Shapes.box(1.0D / 16, 9.0D / 16, 1.0D / 16, 15.0D / 16, 11.0D / 16, 15.0D / 16)
+ );
+ }
}
diff --git a/src/main/java/com/chemiofitor/protection_engineering/client/PEHormoneOverlay.java b/src/main/java/com/chemiofitor/protection_engineering/client/PEHormoneOverlay.java
index 4ad0174..2ea3ac0 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/client/PEHormoneOverlay.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/client/PEHormoneOverlay.java
@@ -3,6 +3,7 @@
import com.chemiofitor.protection_engineering.ProtectionEngineering;
import com.chemiofitor.protection_engineering.api.IAttachment;
import com.chemiofitor.protection_engineering.api.IAttachmentHost;
+import com.chemiofitor.protection_engineering.config.PEServerConfig;
import com.chemiofitor.protection_engineering.item.SpyglassItem;
import com.chemiofitor.protection_engineering.registry.PEDataComponents;
import com.chemiofitor.protection_engineering.registry.PEItems;
@@ -121,7 +122,7 @@ private static float getOverlayAlpha(LocalPlayer player) {
long cooldownEnd = getHormoneCooldownEnd(player);
if (cooldownEnd <= 0) return 0;
- long activatedAt = cooldownEnd - 1200; // COOLDOWN_TICKS
+ long activatedAt = cooldownEnd - PEServerConfig.HORMONE_COOLDOWN_TICKS.get();
long elapsed = now - activatedAt;
if (elapsed < 0 || elapsed >= 200) return 0; // EFFECT_DURATION = 200
@@ -156,7 +157,7 @@ private static float getFovBonus(LocalPlayer player) {
long cooldownEnd = getHormoneCooldownEnd(player);
if (cooldownEnd <= 0) return 0;
- long activatedAt = cooldownEnd - 1200;
+ long activatedAt = cooldownEnd - PEServerConfig.HORMONE_COOLDOWN_TICKS.get();
long elapsed = now - activatedAt;
if (elapsed < 0 || elapsed >= 60) return 0;
diff --git a/src/main/java/com/chemiofitor/protection_engineering/compat/DetailArmorBarCompat.java b/src/main/java/com/chemiofitor/protection_engineering/compat/DetailArmorBarCompat.java
new file mode 100644
index 0000000..5d09382
--- /dev/null
+++ b/src/main/java/com/chemiofitor/protection_engineering/compat/DetailArmorBarCompat.java
@@ -0,0 +1,35 @@
+package com.chemiofitor.protection_engineering.compat;
+
+import com.chemiofitor.protection_engineering.ProtectionEngineering;
+import com.chemiofitor.protection_engineering.registry.PEItems;
+import com.redlimerl.detailab.api.DetailArmorBarAPI;
+import com.redlimerl.detailab.api.render.ArmorBarRenderManager;
+import com.redlimerl.detailab.api.render.BarRenderManager;
+import com.redlimerl.detailab.api.render.TextureOffset;
+import net.minecraft.resources.ResourceLocation;
+import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
+
+import java.awt.*;
+
+public class DetailArmorBarCompat {
+
+ private static final ResourceLocation TEX = ResourceLocation.fromNamespaceAndPath(
+ ProtectionEngineering.MODID, "textures/gui/engineer_armor_bar.png");
+ // 纹理 36×9: 半格(0,0) 满格(9,0) 半轮廓(18,0) 全轮廓(27,0)
+
+ public static void onClientSetup(FMLClientSetupEvent event) {
+ event.enqueueWork(() -> DetailArmorBarAPI.customArmorBarBuilder()
+ .armor(PEItems.ENGINEER_HOOD.get(),
+ PEItems.ENGINEER_CHESTPLATE.get(),
+ PEItems.ENGINEER_LEGGINGS.get(),
+ PEItems.ENGINEER_BOOTS.get())
+ .render(stack -> new ArmorBarRenderManager(
+ new BarRenderManager.Texture(TEX, 64, 32, new TextureOffset(9, 0)),
+ new BarRenderManager.Texture(TEX, 64, 32, new TextureOffset(0, 0)),
+ new BarRenderManager.Texture(TEX, 64, 32, new TextureOffset(27, 0)),
+ new BarRenderManager.Texture(TEX, 64, 32, new TextureOffset(18, 0)),
+ Color.WHITE
+ ))
+ .register());
+ }
+}
diff --git a/src/main/java/com/chemiofitor/protection_engineering/entity/MissileEntity.java b/src/main/java/com/chemiofitor/protection_engineering/entity/MissileEntity.java
index 83b0123..4c7cc88 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/entity/MissileEntity.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/entity/MissileEntity.java
@@ -358,6 +358,8 @@ protected void defineSynchedData(SynchedEntityData.Builder builder) {
protected void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.life = tag.getInt("Life");
+ this.engineSoundTimer = tag.getInt("EngineSound");
+ this.warningSoundTimer = tag.getInt("WarningSound");
if (tag.hasUUID("TargetUUID")) {
this.targetEntityUUID = tag.getUUID("TargetUUID");
}
@@ -376,6 +378,8 @@ protected void readAdditionalSaveData(CompoundTag tag) {
protected void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt("Life", life);
+ tag.putInt("EngineSound", engineSoundTimer);
+ tag.putInt("WarningSound", warningSoundTimer);
if (targetEntityUUID != null) {
tag.putUUID("TargetUUID", targetEntityUUID);
}
diff --git a/src/main/java/com/chemiofitor/protection_engineering/entity/ThrustEntity.java b/src/main/java/com/chemiofitor/protection_engineering/entity/ThrustEntity.java
index 447102a..a2bd877 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/entity/ThrustEntity.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/entity/ThrustEntity.java
@@ -53,13 +53,17 @@ public void init(LivingEntity target, double targetSpeed, boolean soulFire, int
@Override
public boolean shouldRender(double x, double y, double z) { return false; }
+ @Override
+ public void remove(RemovalReason reason) {
+ if (target != null) ACTIVE.remove(target.getUUID());
+ super.remove(reason);
+ }
+
@Override
public void tick() {
super.tick();
if (target == null || !target.isAlive() || life >= maxLife) {
- ProtectionEngineering.LOGGER.info("Thrust DISCARD life={}/{} targetNull={} targetDead={}",
- life, maxLife, target == null, target != null && !target.isAlive());
- if (target != null) ACTIVE.remove(target.getUUID());
+ ProtectionEngineering.LOGGER.debug("Thrust DISCARD life={}/{}", life, maxLife);
this.discard();
return;
}
@@ -75,11 +79,6 @@ public void tick() {
look.x * 0.1 + (look.x * targetSpeed - vel.x) * 0.5,
look.y * 0.1 + (look.y * targetSpeed - vel.y) * 0.5,
look.z * 0.1 + (look.z * targetSpeed - vel.z) * 0.5));
- if (life % 20 == 0) {
- Vec3 v = target.getDeltaMovement();
- ProtectionEngineering.LOGGER.info("Thrust life={}/{} thrust={} vx={} vy={} vz={} fly={}",
- life, maxLife, targetSpeed, v.x, v.y, v.z, target.isFallFlying());
- }
// 粒子
if (target.level() instanceof ServerLevel sl) {
@@ -89,7 +88,6 @@ public void tick() {
pos.x, pos.y, pos.z, 3, 0.3, 0.05, 0.3, 0.02);
}
} else {
- ACTIVE.remove(target.getUUID());
this.discard();
return;
}
diff --git a/src/main/java/com/chemiofitor/protection_engineering/event/PEGameEvents.java b/src/main/java/com/chemiofitor/protection_engineering/event/PEGameEvents.java
index 741b4dd..310edc6 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/event/PEGameEvents.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/event/PEGameEvents.java
@@ -15,6 +15,7 @@
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ItemStack;
@@ -203,4 +204,43 @@ private static boolean hasImmunity(Player player, Holder effect) {
}
return false;
}
+
+ // ── 装备变更(LivingEquipmentChangeEvent) ──────────────────
+
+ /** 由 ProtectionEngineering 构造器注册到 NeoForge.EVENT_BUS */
+ public static final class EquipmentHandler {
+ @SubscribeEvent
+ public void onEquipmentChange(net.neoforged.neoforge.event.entity.living.LivingEquipmentChangeEvent event) {
+ var slot = event.getSlot();
+ if (slot.getType() != EquipmentSlot.Type.HUMANOID_ARMOR) return;
+
+ ItemStack from = event.getFrom();
+ ItemStack to = event.getTo();
+
+
+ LivingEntity entity = event.getEntity();
+
+ // 卸下
+ if (from.getItem() instanceof IAttachmentHost) {
+ for (var entry : ((IAttachmentHost) from.getItem()).getAttachments(from).slots().entrySet()) {
+ if (entry.getValue().getItem() instanceof IAttachment att) {
+ att.onUnequip(entry.getValue(), from, entity);
+ }
+ }
+ }
+
+ // 装备
+ if (to.getItem() instanceof IAttachmentHost host) {
+ var data = host.getAttachments(to);
+ for (var entry : data.slots().entrySet()) {
+ var attStack = entry.getValue();
+ if (attStack.getItem() instanceof IAttachment att) {
+ att.onEquip(attStack, to, entity);
+ data = data.with(entry.getKey(), attStack);
+ }
+ }
+ host.setAttachments(to, data);
+ }
+ }
+ }
}
diff --git a/src/main/java/com/chemiofitor/protection_engineering/event/PENetworkEvents.java b/src/main/java/com/chemiofitor/protection_engineering/event/PENetworkEvents.java
index e65cdf2..3ec82b1 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/event/PENetworkEvents.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/event/PENetworkEvents.java
@@ -31,7 +31,9 @@ public static void registerPayloads(RegisterPayloadHandlersEvent event) {
(payload, context) -> context.enqueueWork(() -> {
var player = context.player();
- EquipmentSlot slot = EquipmentSlot.values()[payload.armorIndex()];
+ int idx = payload.armorIndex();
+ if (idx < 0 || idx >= EquipmentSlot.values().length) return;
+ EquipmentSlot slot = EquipmentSlot.values()[idx];
ItemStack armor = player.getItemBySlot(slot);
if (!(armor.getItem() instanceof IAttachmentHost host)) return;
@@ -44,8 +46,6 @@ public static void registerPayloads(RegisterPayloadHandlersEvent event) {
attItem.onActivatePress(attachment, armor, player);
}
host.setAttachments(armor, data.with(type, attachment));
- // 装备数据修改后须写回槽位,触发客户端同步
- player.setItemSlot(slot, armor);
})
);
diff --git a/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentHostArmorItem.java b/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentHostArmorItem.java
index 86de53a..4dce033 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentHostArmorItem.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentHostArmorItem.java
@@ -8,7 +8,6 @@
import net.minecraft.core.Holder;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
-import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ArmorItem;
@@ -17,9 +16,7 @@
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
-import java.util.IdentityHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import static com.chemiofitor.protection_engineering.registry.PEDataComponents.ATTACHMENTS;
@@ -69,71 +66,29 @@ public AttachmentsData getAttachments(ItemStack host) {
public void setAttachments(ItemStack host, AttachmentsData data) {
host.set(ATTACHMENTS.get(), data);
}
-
// ── Tick ────────────────────────────────────────────────────
- private static final Map WORN_TRACKER = new IdentityHashMap<>();
-
@Override
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean isSelected) {
if (!(entity instanceof LivingEntity living)) return;
-
- boolean isWorn = living.getItemBySlot(getEquipmentSlot()) == stack;
- boolean wasWorn = WORN_TRACKER.getOrDefault(stack, false);
-
- if (!wasWorn && isWorn) {
- // 刚穿上 → 通知所有附件
- AttachmentsData data = getAttachments(stack);
- for (var entry : data.slots().entrySet()) {
- ItemStack attached = entry.getValue();
- if (attached.getItem() instanceof IAttachment att) {
- att.onEquip(attached, stack, living);
- data = data.with(entry.getKey(), attached);
- }
- }
- setAttachments(stack, data);
- syncEquipmentIfPlayer(living, stack);
- } else if (wasWorn && !isWorn) {
- // 刚脱下 → 通知所有附件
- AttachmentsData data = getAttachments(stack);
- for (var entry : data.slots().entrySet()) {
- ItemStack attached = entry.getValue();
- if (attached.getItem() instanceof IAttachment att) {
- att.onUnequip(attached, stack, living);
- }
- }
- }
-
- WORN_TRACKER.put(stack, isWorn);
-
- if (!isWorn) return;
-
- // 服务端 tick + 写回;客户端自动从 networkSynchronized 数据组件同步
+ if (living.getItemBySlot(getEquipmentSlot()) != stack) return;
if (level.isClientSide()) return;
AttachmentsData data = getAttachments(stack);
boolean mutated = false;
for (var entry : data.slots().entrySet()) {
- ItemStack attachmentStack = entry.getValue();
+ var key = entry.getKey();
+ ItemStack attachmentStack = entry.getValue().copy();
if (attachmentStack.getItem() instanceof IAttachment attachment) {
- attachment.onTick(attachmentStack, stack, living, entry.getKey());
- data = data.with(entry.getKey(), attachmentStack);
- mutated = true;
+ attachment.onTick(attachmentStack, stack, living, key);
+ if (!ItemStack.matches(entry.getValue(), attachmentStack)) {
+ data = data.with(key, attachmentStack);
+ mutated = true;
+ }
}
}
if (mutated) {
setAttachments(stack, data);
- syncEquipmentIfPlayer(living, stack);
- }
- }
-
- /** 数据组件变更后强制同步到客户端 */
- private void syncEquipmentIfPlayer(LivingEntity living, ItemStack stack) {
- if (living instanceof ServerPlayer sp) {
- sp.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket(
- sp.getId(), List.of(
- com.mojang.datafixers.util.Pair.of(getEquipmentSlot(), stack.copy())
- )));
}
}
diff --git a/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentItem.java b/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentItem.java
index a13ef99..ddf5cc1 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentItem.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/item/AttachmentItem.java
@@ -82,9 +82,12 @@ private int migrateState(ItemStack stack) {
return getControlPattern() == ControlPattern.ALWAYS_ON ? STATE_READY : STATE_DISABLED;
}
switch (getControlPattern()) {
- case ACTIVE_COOLDOWN:
- // 保守迁移为 READY,首个 tick 修正
- return STATE_READY;
+ case ACTIVE_COOLDOWN: {
+ // 有旧冷却计时器 → 迁移到 COOLING
+ long oldTimer = stack.getOrDefault(ATTACHMENT_COOLDOWN.get(), 0L);
+ if (!active && oldTimer > 0) return STATE_COOLING;
+ return STATE_READY; // 激活窗口中或就绪,下个 tick 修正
+ }
case ONE_SHOT_COOLDOWN:
return active ? STATE_READY : STATE_COOLING;
case FREE_TOGGLE:
@@ -159,12 +162,17 @@ public void onTick(ItemStack attachment, ItemStack host, LivingEntity entity, Sl
long timer = getTimer(attachment);
int state = getState(attachment);
- if (timer == 0) return;
+ // 损坏恢复:需要计时器但没有 → 回到就绪
+ if (timer == 0) {
+ if (state == STATE_ACTIVE || state == STATE_COOLING) {
+ setState(attachment, STATE_READY);
+ }
+ return;
+ }
switch (state) {
case STATE_ACTIVE:
if (now >= timer) {
- // 激活窗口到期 → 进入冷却
setState(attachment, STATE_COOLING);
setTimer(attachment, now + getCooldownDuration());
onStateExit(attachment, STATE_ACTIVE, entity);
@@ -173,7 +181,6 @@ public void onTick(ItemStack attachment, ItemStack host, LivingEntity entity, Sl
case STATE_COOLING:
if (now >= timer) {
- // 冷却到期 → 回到就绪
setState(attachment, STATE_READY);
onStateEnter(attachment, STATE_READY, entity);
}
diff --git a/src/main/java/com/chemiofitor/protection_engineering/menu/WorkbenchMenu.java b/src/main/java/com/chemiofitor/protection_engineering/menu/WorkbenchMenu.java
index 1f5386d..596c80c 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/menu/WorkbenchMenu.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/menu/WorkbenchMenu.java
@@ -126,7 +126,7 @@ private void loadFromArmor() {
}
private void playAttachSound() {
- if (be == null) return;
+ if (be == null || be.getLevel() == null) return;
var sound = RandomSource.create().nextBoolean()
? PESounds.ATTACH_1.get() : PESounds.ATTACH_2.get();
be.getLevel().playSound(null, be.getBlockPos(), sound, SoundSource.BLOCKS, 0.8F, 1.0F);
@@ -144,17 +144,6 @@ private void applyToArmor() {
syncing = false;
}
- /** 菜单关闭 → 写回 BE 并清空 */
- public void onClosed() {
- if (be == null) return;
- // 附件写入护甲
- for (int i = 0; i < WorkbenchBlockEntity.SLOT_COUNT; i++)
- be.setItem(i, container.getItem(i).copy());
- be.applyAttachments();
- // 清空 BE 物品栏(不持久化)
- be.clearContent();
- }
-
// ── Screen ─────────────────────────────────────────────────
public List getActiveSlots() {
diff --git a/src/main/java/com/chemiofitor/protection_engineering/registry/PEArmorMaterials.java b/src/main/java/com/chemiofitor/protection_engineering/registry/PEArmorMaterials.java
index 672cb0b..1ad8fc6 100644
--- a/src/main/java/com/chemiofitor/protection_engineering/registry/PEArmorMaterials.java
+++ b/src/main/java/com/chemiofitor/protection_engineering/registry/PEArmorMaterials.java
@@ -36,7 +36,7 @@ public class PEArmorMaterials {
ArmorItem.Type.BOOTS, 3
),
15, // 附魔值同下界合金
- PESounds.EQUIP_ENGINEER_ARMOR, // 工程师护甲音效(复用下界合金音频)
+ PESounds.EQUIP_ENGINEER_ARMOR, // 工程师护甲音效
() -> Ingredient.of(Items.IRON_INGOT),
List.of(new ArmorMaterial.Layer(ProtectionEngineering.asResource("engineer"))),
3.0F, // 韧性同下界合金
diff --git a/src/main/resources/assets/protectionengineering/sounds.json b/src/main/resources/assets/protectionengineering/sounds.json
index 5f8530b..cc2c2a4 100644
--- a/src/main/resources/assets/protectionengineering/sounds.json
+++ b/src/main/resources/assets/protectionengineering/sounds.json
@@ -6,7 +6,12 @@
"sounds": ["protectionengineering:attach_2"]
},
"equip_engineer_armor": {
- "sounds": []
+ "sounds": [
+ "item/armor/equip_netherite1",
+ "item/armor/equip_netherite2",
+ "item/armor/equip_netherite3",
+ "item/armor/equip_netherite4"
+ ]
},
"night_vision_on": {
"sounds": ["protectionengineering:night_vision_on"]
diff --git a/src/main/resources/assets/protectionengineering/textures/gui/engineer_armor_bar.png b/src/main/resources/assets/protectionengineering/textures/gui/engineer_armor_bar.png
new file mode 100644
index 0000000..d78a962
Binary files /dev/null and b/src/main/resources/assets/protectionengineering/textures/gui/engineer_armor_bar.png differ