Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Protection Engineering · 工程防护

![展示](doc/展示.png)
Minecraft NeoForge 1.21.1 模组。为工程师护甲系统提供**可安装附件**,每个附件安装在护甲的特定槽位上,提供被动免疫、属性加成或主动技能。依赖 **Create** 模组。

策划/美术:Rhodes_Koei
Expand Down Expand Up @@ -84,6 +85,14 @@ Minecraft NeoForge 1.21.1 模组。为工程师护甲系统提供**可安装附
|------|------|
| 改良鞋底 | 防滑,免疫粘液/蜂蜜块/灵魂沙减速,细雪行走 |
| 缓冲鞋底 | 摔落伤害 -20%,等效摔落高度 -1 |
| 隔热鞋底 | 免疫岩浆块伤害 |

### 武器 / 工具

| 物品 | 功能 |
|------|------|
| 工程师链锯剑 | 伤害/攻速等同钻石剑,耐久等同下界合金,可砍树(斧头),兼容剑+斧附魔 |
| 工程师盾牌 | 3×原版耐久,斧破防冷却减半,防护范围 +20% |

### 消耗品

Expand Down
Binary file added doc/展示.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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.3
mod_version=1.0.4
# 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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 1.21.1 2026-06-23T07:38:15.5661198 Registrate Provider for protectionengineering [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (blocks), Tags (enchantments), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider]
// 1.21.1 2026-06-24T00:07:46.5949336 Registrate Provider for protectionengineering [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (blocks), Tags (enchantments), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider]
9548b733d8408aab37f04a6ccfcda663b4d8e229 assets/protectionengineering/blockstates/workbench.json
cecaa1fb9034d4ff8edd8246b69866306c6ffc4b assets/protectionengineering/lang/en_ud.json
32e3e427d848ba7a9ee7ed826e8907166b7e71d9 assets/protectionengineering/lang/en_us.json
3ebc6bada8dee1c71143cd590a503026603d9e78 assets/protectionengineering/lang/en_ud.json
50483ba261acea55871e79e9c15e6255fd297291 assets/protectionengineering/lang/en_us.json
de432a9aaddd8bf94d4ff0714405d45b68482019 assets/protectionengineering/models/item/advanced_aps.json
fd6421744d19fc737d2db860c16f73d268b23727 assets/protectionengineering/models/item/air_filter.json
d83a0d8a91c9426b9002ffad467f65ec884026bb assets/protectionengineering/models/item/aps.json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"item.protectionengineering.engineer_goggles": "sǝןbbo⅁ ɹǝǝuıbuƎ",
"item.protectionengineering.engineer_hood": "pooH ɹǝǝuıbuƎ",
"item.protectionengineering.engineer_leggings": "sbuıbbǝꞀ ɹǝǝuıbuƎ",
"item.protectionengineering.engineer_saw_sword": "pɹoʍS ʍɐS ɹǝǝuıbuƎ",
"item.protectionengineering.extra_mechanical_arm": "ɯɹⱯ ןɐɔıuɐɥɔǝW ɐɹʇxƎ",
"item.protectionengineering.heavy_exoskeleton": "uoʇǝןǝʞsoxƎ ʎʌɐǝH",
"item.protectionengineering.hormone_injector": "ɹoʇɔǝظuI ǝuoɯɹoH",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"item.protectionengineering.engineer_goggles": "Engineer Goggles",
"item.protectionengineering.engineer_hood": "Engineer Hood",
"item.protectionengineering.engineer_leggings": "Engineer Leggings",
"item.protectionengineering.engineer_saw_sword": "Engineer Saw Sword",
"item.protectionengineering.extra_mechanical_arm": "Extra Mechanical Arm",
"item.protectionengineering.heavy_exoskeleton": "Heavy Exoskeleton",
"item.protectionengineering.hormone_injector": "Hormone Injector",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
package com.chemiofitor.protection_engineering;

import net.minecraft.client.Minecraft;
import com.chemiofitor.protection_engineering.registry.PEItems;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.client.gui.ConfigurationScreen;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
import net.neoforged.bus.api.SubscribeEvent;

@Mod(value = ProtectionEngineering.MODID, dist = Dist.CLIENT)
public class ProtectionEngineeringClient {

public ProtectionEngineeringClient(ModContainer container) {
container.getEventBus().register(this);
}

@SubscribeEvent
private void onClientSetup(FMLClientSetupEvent event) {
event.enqueueWork(() -> {
ItemProperties.register(PEItems.ENGINEER_SHIELD.get(),
ResourceLocation.withDefaultNamespace("blocking"),
(stack, level, entity, seed) ->
entity != null && entity.isUsingItem() && entity.getUseItem() == stack ? 1.0F : 0.0F);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.chemiofitor.protection_engineering.item;

import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.*;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.neoforged.neoforge.common.ItemAbilities;
import net.neoforged.neoforge.common.ItemAbility;

import java.util.Optional;

/**
* 工程师链锯剑 — 等同钻石剑伤害/攻速,下界合金耐久。
* 攻击冷却未满也能横扫,可砍树(斧头工具),兼容剑+斧附魔。
*/
public class EngineerSawSwordItem extends SwordItem {

/** 与钻石剑相同的属性,耐久用下界合金 */
public static final Tier TIER = new Tier() {
@Override public int getUses() { return 2031; } // 下界合金耐久
@Override public float getSpeed() { return 8.0F; } // 钻石工具挖掘速度
@Override public float getAttackDamageBonus() { return 3.0F; } // 钻石剑 +3
@Override public int getEnchantmentValue() { return 15; } // 钻石附魔能力
@Override public net.minecraft.tags.TagKey<net.minecraft.world.level.block.Block> getIncorrectBlocksForDrops() {
return net.minecraft.tags.BlockTags.INCORRECT_FOR_DIAMOND_TOOL;
}
@Override public net.minecraft.world.item.crafting.Ingredient getRepairIngredient() {
return net.minecraft.world.item.crafting.Ingredient.of(Items.DIAMOND);
}
};

public EngineerSawSwordItem(Properties properties) {
super(TIER, properties.attributes(SwordItem.createAttributes(TIER, 3.0F, -2.4F)));
}

// ── 视为斧头(砍树) ──────────────────────────────────────

@Override
public boolean isCorrectToolForDrops(ItemStack stack, BlockState state) {
return state.is(net.minecraft.tags.BlockTags.MINEABLE_WITH_AXE)
|| super.isCorrectToolForDrops(stack, state);
}

@Override
public float getDestroySpeed(ItemStack stack, BlockState state) {
if (state.is(net.minecraft.tags.BlockTags.MINEABLE_WITH_AXE)) {
return TIER.getSpeed();
}
return super.getDestroySpeed(stack, state);
}

// ── 斧头动作:useOn 剥皮/刮铜/去蜡 ────────────────────────

@Override
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
BlockPos pos = context.getClickedPos();
Player player = context.getPlayer();
BlockState state = level.getBlockState(pos);

Optional<BlockState> stripped = Optional.ofNullable(
state.getToolModifiedState(context, ItemAbilities.AXE_STRIP, false));
if (stripped.isPresent()) {
level.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F);
} else {
stripped = Optional.ofNullable(
state.getToolModifiedState(context, ItemAbilities.AXE_SCRAPE, false));
if (stripped.isPresent()) {
level.playSound(player, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F);
level.levelEvent(player, 3005, pos, 0);
} else {
stripped = Optional.ofNullable(
state.getToolModifiedState(context, ItemAbilities.AXE_WAX_OFF, false));
if (stripped.isPresent()) {
level.playSound(player, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F);
level.levelEvent(player, 3004, pos, 0);
}
}
}

if (stripped.isPresent()) {
if (player instanceof ServerPlayer sp) {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(sp, pos, context.getItemInHand());
}
level.setBlock(pos, stripped.get(), 11);
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(player, stripped.get()));
if (player != null) {
context.getItemInHand().hurtAndBreak(1, player,
LivingEntity.getSlotForHand(context.getHand()));
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
return InteractionResult.PASS;
}

@Override
public boolean canPerformAction(ItemStack stack, ItemAbility ability) {
return ItemAbilities.DEFAULT_AXE_ACTIONS.contains(ability)
|| ItemAbilities.DEFAULT_SWORD_ACTIONS.contains(ability);
}

// ── 兼容剑+斧附魔 ─────────────────────────────────────────

@Override
public boolean isEnchantable(ItemStack stack) { return true; }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.chemiofitor.protection_engineering.item;

import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.DispenserBlock;

/**
* 工程师盾牌 — 3×耐久,斧头破防禁用减半,防护范围 +20%。
*/
public class EngineerShieldItem extends Item implements Equipable {

public static final int COOLDOWN_TICKS = 50; // 原版 100 的一半

public EngineerShieldItem(Properties properties) {
super(properties);
DispenserBlock.registerBehavior(this, ArmorItem.DISPENSE_ITEM_BEHAVIOR);
}

@Override
public UseAnim getUseAnimation(ItemStack stack) {
return UseAnim.BLOCK;
}

@Override
public int getUseDuration(ItemStack stack, LivingEntity entity) {
return 72000;
}

@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
ItemStack itemstack = player.getItemInHand(hand);
player.startUsingItem(hand);
return InteractionResultHolder.consume(itemstack);
}

@Override
public boolean isValidRepairItem(ItemStack toRepair, ItemStack repair) {
return repair.is(net.minecraft.tags.ItemTags.PLANKS) || super.isValidRepairItem(toRepair, repair);
}

@Override
public EquipmentSlot getEquipmentSlot() {
return EquipmentSlot.OFFHAND;
}

@Override
public boolean canPerformAction(ItemStack stack, net.neoforged.neoforge.common.ItemAbility itemAbility) {
return net.neoforged.neoforge.common.ItemAbilities.DEFAULT_SHIELD_ACTIONS.contains(itemAbility);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.chemiofitor.protection_engineering.mixin;

import com.chemiofitor.protection_engineering.item.EngineerShieldItem;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;

@Mixin(Player.class)
public class PlayerShieldMixin {

/** 工程师盾牌斧头破防禁用时间减半:100 → 50 tick */
@ModifyArg(method = "disableShield", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/item/ItemCooldowns;addCooldown(Lnet/minecraft/world/item/Item;I)V"),
index = 1)
private int pe_modifyShieldCooldown(int originalDuration) {
Player self = (Player) (Object) this;
ItemStack useItem = self.getUseItem();
if (useItem.getItem() instanceof EngineerShieldItem) {
return EngineerShieldItem.COOLDOWN_TICKS;
}
return originalDuration;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.chemiofitor.protection_engineering.mixin;

import com.chemiofitor.protection_engineering.item.EngineerShieldItem;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(LivingEntity.class)
public class ShieldAngleMixin {

/** 工程师盾牌防护范围 +20%(180° → 216°) */
@Inject(method = "isDamageSourceBlocked", at = @At("HEAD"), cancellable = true)
private void pe_widenShieldAngle(DamageSource source, CallbackInfoReturnable<Boolean> cir) {
LivingEntity self = (LivingEntity) (Object) this;
if (!(self instanceof Player player)) return;
if (!(player.getUseItem().getItem() instanceof EngineerShieldItem)) return;
if (!player.isBlocking()) return;

if (source.is(net.minecraft.tags.DamageTypeTags.BYPASSES_SHIELD)) return;
if (source.getDirectEntity() instanceof net.minecraft.world.entity.projectile.AbstractArrow a
&& a.getPierceLevel() > 0) return;

Vec3 srcPos = source.getSourcePosition();
if (srcPos == null) return;

Vec3 look = player.calculateViewVector(0.0F, player.getYHeadRot());
Vec3 toPlayer = srcPos.vectorTo(player.position());
toPlayer = new Vec3(toPlayer.x, 0.0, toPlayer.z).normalize();

// 原版 < 0.0(180°),加宽到 < 0.309(216°)
cir.setReturnValue(toPlayer.dot(look) < 0.309);
}
}
Loading
Loading