the effect full suite

This commit is contained in:
Darto KLoning
2026-09-08 18:11:29 +07:00
parent 1fa61d38e6
commit 344b7d1a41
10 changed files with 191 additions and 11 deletions
+9
View File
@@ -53,3 +53,12 @@ Dedicated-server runtime verification remains a later test step; this iteration
- After a successful build, the script can commit and push the repository. - After a successful build, the script can commit and push the repository.
- Git push retries automatically up to three times per round; after three failures the user can choose another three-attempt round or stop. - Git push retries automatically up to three times per round; after three failures the user can choose another three-attempt round or stop.
- Added `.gitignore` for Gradle caches, build output, Loom runtime files, IDE metadata, and logs so Git integration does not stage generated files. - Added `.gitignore` for Gradle caches, build output, Loom runtime files, IDE metadata, and logs so Git integration does not stage generated files.
## 0.9.0-alpha - Full-set armor effects
- Attacker full set: `Better Armor` effect icon, +15 armor toughness, +0.25 knockback resistance.
- Scout full set: `Lightweight` effect icon, +0.025 movement speed.
- Defender full set: `Heavy Armor` effect icon, -0.02 movement speed, and final health damage reduced to 85%.
- Medical full set: `Self-Healing Device` ready effect; below 30% health triggers Regeneration IV for 5 seconds and a 30-second self-healing cooldown.
- Reused the original mod's mob-effect icons (`tough`, `heavy_armor`, `light_leg`, `rescue`, `rescue_cooldown`).
- Full-set logic runs server-side so it can later be tested on a dedicated Fabric server as well as integrated singleplayer.
+1 -1
View File
@@ -8,6 +8,6 @@ loom_version=1.17-SNAPSHOT
fabric_api_version=0.158.0+26.2 fabric_api_version=0.158.0+26.2
geckolib_version=5.5.4 geckolib_version=5.5.4
mod_version=0.8.0-alpha mod_version=0.9.0-alpha
maven_group=me.xjqsh maven_group=me.xjqsh
archives_base_name=lrarmor archives_base_name=lrarmor
@@ -1,6 +1,8 @@
package me.xjqsh.lesraisinsarmor; package me.xjqsh.lesraisinsarmor;
import me.xjqsh.lesraisinsarmor.init.ModItems; import me.xjqsh.lesraisinsarmor.init.ModItems;
import me.xjqsh.lesraisinsarmor.effect.ModEffects;
import me.xjqsh.lesraisinsarmor.gameplay.ArmorSetEffects;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -11,7 +13,9 @@ public final class LesRaisinsArmor implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
ModEffects.initialize();
ModItems.initialize(); ModItems.initialize();
ArmorSetEffects.initialize();
LOGGER.info("LesRaisinsArmor Fabric 26.2 port initialized ({} armor pieces).", ModItems.ARMOR_ITEMS.size()); LOGGER.info("LesRaisinsArmor Fabric 26.2 port initialized ({} armor pieces).", ModItems.ARMOR_ITEMS.size());
} }
} }
@@ -0,0 +1,53 @@
package me.xjqsh.lesraisinsarmor.effect;
import me.xjqsh.lesraisinsarmor.LesRaisinsArmor;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
public final class ModEffects {
private ModEffects() {}
public static final Holder<MobEffect> TOUGH = Registry.registerForHolder(
BuiltInRegistries.MOB_EFFECT, id("tough"),
new ArmorSetEffect(MobEffectCategory.BENEFICIAL, 0x59646f)
.addAttributeModifier(Attributes.ARMOR_TOUGHNESS, id("toughness"), 15.0, AttributeModifier.Operation.ADD_VALUE)
.addAttributeModifier(Attributes.KNOCKBACK_RESISTANCE, id("knockback_resistance"), 0.25, AttributeModifier.Operation.ADD_VALUE));
public static final Holder<MobEffect> LIGHT_LEG = Registry.registerForHolder(
BuiltInRegistries.MOB_EFFECT, id("light_leg"),
new ArmorSetEffect(MobEffectCategory.BENEFICIAL, 0x76c7a2)
.addAttributeModifier(Attributes.MOVEMENT_SPEED, id("scout_speed"), 0.025, AttributeModifier.Operation.ADD_VALUE));
public static final Holder<MobEffect> HEAVY_ARMOR = Registry.registerForHolder(
BuiltInRegistries.MOB_EFFECT, id("heavy_armor"),
new ArmorSetEffect(MobEffectCategory.BENEFICIAL, 0x55585e)
.addAttributeModifier(Attributes.MOVEMENT_SPEED, id("defender_speed"), -0.02, AttributeModifier.Operation.ADD_VALUE));
public static final Holder<MobEffect> RESCUE = Registry.registerForHolder(
BuiltInRegistries.MOB_EFFECT, id("rescue"),
new ArmorSetEffect(MobEffectCategory.BENEFICIAL, 0xb84c4c));
public static final Holder<MobEffect> RESCUE_COOLDOWN = Registry.registerForHolder(
BuiltInRegistries.MOB_EFFECT, id("rescue_cooldown"),
new ArmorSetEffect(MobEffectCategory.NEUTRAL, 0x6f6f6f));
private static Identifier id(String path) {
return Identifier.fromNamespaceAndPath(LesRaisinsArmor.MOD_ID, path);
}
public static void initialize() {
// Class loading performs registry registration.
}
private static final class ArmorSetEffect extends MobEffect {
private ArmorSetEffect(MobEffectCategory category, int color) {
super(category, color);
}
}
}
@@ -0,0 +1,81 @@
package me.xjqsh.lesraisinsarmor.gameplay;
import me.xjqsh.lesraisinsarmor.effect.ModEffects;
import me.xjqsh.lesraisinsarmor.item.LrArmorItem;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
public final class ArmorSetEffects {
private static final int SET_EFFECT_DURATION = 40;
private static final int REFRESH_THRESHOLD = 25;
private static final int RESCUE_COOLDOWN_TICKS = 30 * 20;
private static final int RESCUE_REGEN_TICKS = 5 * 20;
private ArmorSetEffects() {}
public static void initialize() {
ServerTickEvents.END_SERVER_TICK.register(server -> {
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
tickPlayer(player);
}
});
}
private static void tickPlayer(ServerPlayer player) {
syncSetEffect(player, "attacker", ModEffects.TOUGH);
syncSetEffect(player, "scout", ModEffects.LIGHT_LEG);
syncSetEffect(player, "defender", ModEffects.HEAVY_ARMOR);
if (hasFullSet(player, "medical")) {
tickMedical(player);
} else {
player.removeEffect(ModEffects.RESCUE);
}
}
private static void tickMedical(ServerPlayer player) {
if (player.hasEffect(ModEffects.RESCUE_COOLDOWN)) {
player.removeEffect(ModEffects.RESCUE);
return;
}
refreshEffect(player, ModEffects.RESCUE);
if (player.getHealth() < player.getMaxHealth() * 0.30f) {
player.removeEffect(ModEffects.RESCUE);
player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, RESCUE_REGEN_TICKS, 3, false, true, true));
player.addEffect(new MobEffectInstance(ModEffects.RESCUE_COOLDOWN, RESCUE_COOLDOWN_TICKS, 0, false, false, true));
}
}
private static void syncSetEffect(ServerPlayer player, String suit, net.minecraft.core.Holder<MobEffect> effect) {
if (hasFullSet(player, suit)) {
refreshEffect(player, effect);
} else {
player.removeEffect(effect);
}
}
private static void refreshEffect(ServerPlayer player, net.minecraft.core.Holder<MobEffect> effect) {
MobEffectInstance current = player.getEffect(effect);
if (current == null || current.getDuration() < REFRESH_THRESHOLD) {
player.addEffect(new MobEffectInstance(effect, SET_EFFECT_DURATION, 0, false, false, true));
}
}
public static boolean hasFullSet(net.minecraft.world.entity.LivingEntity entity, String suit) {
return isSuitPiece(entity.getItemBySlot(EquipmentSlot.HEAD), suit)
&& isSuitPiece(entity.getItemBySlot(EquipmentSlot.CHEST), suit)
&& isSuitPiece(entity.getItemBySlot(EquipmentSlot.LEGS), suit)
&& isSuitPiece(entity.getItemBySlot(EquipmentSlot.FEET), suit);
}
private static boolean isSuitPiece(ItemStack stack, String suit) {
return stack.getItem() instanceof LrArmorItem armor && armor.suitId().equals(suit);
}
}
@@ -0,0 +1,26 @@
package me.xjqsh.lesraisinsarmor.mixin;
import me.xjqsh.lesraisinsarmor.gameplay.ArmorSetEffects;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(LivingEntity.class)
public abstract class LivingEntityDamageMixin {
@ModifyArg(
method = "actuallyHurt",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setHealth(F)V"),
index = 0
)
private float lrarmor$applyDefenderDamageReduction(float newHealth) {
LivingEntity self = (LivingEntity) (Object) this;
if (!ArmorSetEffects.hasFullSet(self, "defender")) {
return newHealth;
}
float currentHealth = self.getHealth();
float healthDamage = Math.max(0.0f, currentHealth - newHealth);
return currentHealth - healthDamage * 0.85f;
}
}
@@ -28,7 +28,6 @@
"item.lrarmor.medical_chestplate": "MEDICAL-CHEST", "item.lrarmor.medical_chestplate": "MEDICAL-CHEST",
"item.lrarmor.medical_leggings": "MEDICAL-LEGS", "item.lrarmor.medical_leggings": "MEDICAL-LEGS",
"item.lrarmor.medical_boots": "MEDICAL-BOOTS", "item.lrarmor.medical_boots": "MEDICAL-BOOTS",
"item.lrarmor.dea_armed_helmet": "Black Tactical Helmet", "item.lrarmor.dea_armed_helmet": "Black Tactical Helmet",
"item.lrarmor.dea_armed_chestplate": "DEA Chest", "item.lrarmor.dea_armed_chestplate": "DEA Chest",
"item.lrarmor.dea_armed_leggings": "Denim Jeans", "item.lrarmor.dea_armed_leggings": "Denim Jeans",
@@ -65,7 +64,6 @@
"item.lrarmor.joker_chestplate": "\"Joker\" Jacket", "item.lrarmor.joker_chestplate": "\"Joker\" Jacket",
"item.lrarmor.joker_leggings": "\"Joker\" Pants", "item.lrarmor.joker_leggings": "\"Joker\" Pants",
"item.lrarmor.joker_boots": "\"Joker\" Boots", "item.lrarmor.joker_boots": "\"Joker\" Boots",
"suit.lrarmor.armored_chemical": "ARMORED CHEMICAL SUIT", "suit.lrarmor.armored_chemical": "ARMORED CHEMICAL SUIT",
"suit.lrarmor.attacker": "ATTACKER SUIT", "suit.lrarmor.attacker": "ATTACKER SUIT",
"suit.lrarmor.chemical_protective": "CHEMICAL PROTECTIVE SUIT", "suit.lrarmor.chemical_protective": "CHEMICAL PROTECTIVE SUIT",
@@ -82,10 +80,10 @@
"suit.lrarmor.fbi_armed": "FBI Special Suit", "suit.lrarmor.fbi_armed": "FBI Special Suit",
"suit.lrarmor.joker": "\"Joker\" Organization Suit", "suit.lrarmor.joker": "\"Joker\" Organization Suit",
"suit.lrarmor.joker_armed": "\"Joker\" Organization Combat Suit", "suit.lrarmor.joker_armed": "\"Joker\" Organization Combat Suit",
"effect.lrarmor.tough": "Better Armor", "effect.lrarmor.tough": "Better Armor",
"effect.lrarmor.heavy_armor": "Heavy Armor", "effect.lrarmor.heavy_armor": "Heavy Armor",
"effect.lrarmor.light_leg": "Lightweight", "effect.lrarmor.light_leg": "Lightweight",
"effect.lrarmor.rescue": "Self-Healing Device", "effect.lrarmor.rescue": "Self-Healing Device",
"tooltip.lramrmor.suit": "%s: %s" "tooltip.lramrmor.suit": "%s: %s",
"effect.lrarmor.rescue_cooldown": "Self-Healing Cooldown"
} }
@@ -28,7 +28,6 @@
"item.lrarmor.medical_chestplate": "创伤小组-胸甲", "item.lrarmor.medical_chestplate": "创伤小组-胸甲",
"item.lrarmor.medical_leggings": "创伤小组-护腿", "item.lrarmor.medical_leggings": "创伤小组-护腿",
"item.lrarmor.medical_boots": "创伤小组-靴子", "item.lrarmor.medical_boots": "创伤小组-靴子",
"item.lrarmor.dea_armed_helmet": "黑色战术头盔", "item.lrarmor.dea_armed_helmet": "黑色战术头盔",
"item.lrarmor.dea_armed_chestplate": "DEA 防弹衣", "item.lrarmor.dea_armed_chestplate": "DEA 防弹衣",
"item.lrarmor.dea_armed_leggings": "休闲牛仔裤", "item.lrarmor.dea_armed_leggings": "休闲牛仔裤",
@@ -65,7 +64,6 @@
"item.lrarmor.joker_chestplate": "“愚者”礼服上衣", "item.lrarmor.joker_chestplate": "“愚者”礼服上衣",
"item.lrarmor.joker_leggings": "“愚者”礼服长裤", "item.lrarmor.joker_leggings": "“愚者”礼服长裤",
"item.lrarmor.joker_boots": "“愚者”礼服靴子", "item.lrarmor.joker_boots": "“愚者”礼服靴子",
"suit.lrarmor.armored_chemical": "装甲防化服套装", "suit.lrarmor.armored_chemical": "装甲防化服套装",
"suit.lrarmor.attacker": "突击者套装", "suit.lrarmor.attacker": "突击者套装",
"suit.lrarmor.chemical_protective": "防化服套装", "suit.lrarmor.chemical_protective": "防化服套装",
@@ -82,10 +80,10 @@
"suit.lrarmor.fbi_armed": "FBI特别组装甲套装", "suit.lrarmor.fbi_armed": "FBI特别组装甲套装",
"suit.lrarmor.joker": "“愚者”组织套装", "suit.lrarmor.joker": "“愚者”组织套装",
"suit.lrarmor.joker_armed": "“愚者”组织作战套装", "suit.lrarmor.joker_armed": "“愚者”组织作战套装",
"effect.lrarmor.tough": "特制减震夹层", "effect.lrarmor.tough": "特制减震夹层",
"effect.lrarmor.heavy_armor": "超重型全身装甲", "effect.lrarmor.heavy_armor": "超重型全身装甲",
"effect.lrarmor.light_leg": "轻量化腿部支撑", "effect.lrarmor.light_leg": "轻量化腿部支撑",
"effect.lrarmor.rescue": "便携自愈设备", "effect.lrarmor.rescue": "便携自愈设备",
"tooltip.lramrmor.suit": "%s: %s" "tooltip.lramrmor.suit": "%s: %s",
"effect.lrarmor.rescue_cooldown": "自愈装置冷却"
} }
+1
View File
@@ -25,6 +25,7 @@
"geckolib": ">=5.5.4" "geckolib": ">=5.5.4"
}, },
"mixins": [ "mixins": [
"lrarmor.mixins.json",
{ {
"config": "lrarmor.client.mixins.json", "config": "lrarmor.client.mixins.json",
"environment": "client" "environment": "client"
+10
View File
@@ -0,0 +1,10 @@
{
"required": true,
"package": "me.xjqsh.lesraisinsarmor.mixin",
"mixins": [
"LivingEntityDamageMixin"
],
"injectors": {
"defaultRequire": 1
}
}