From ef64fd61f68b0c5d26144cce63408470b12e854b Mon Sep 17 00:00:00 2001 From: lordlogo2002 Date: Fri, 19 Dec 2025 23:20:37 +0100 Subject: [PATCH 1/3] Implement Protection Aura and adjust MepEntity behavior; refactor armor items --- .../chipi/client/hud/ChipiStatusBar.java | 64 +++++++-- .../Chipperfluff/chipi/entity/MepEntity.java | 73 ++++++---- .../Chipperfluff/chipi/entity/SpawnLogic.java | 46 +++++- .../net/Chipperfluff/chipi/item/ModItems.java | 11 +- .../chipi/item/armor/ChipperArmorItem.java | 36 ----- .../item/armor/ChipperArmorMaterial.java | 2 +- .../item/armor/ProtectionAuraHandler.java | 135 ++++++++++++++++++ .../chipi/server/ChipiServerEvents.java | 89 +++--------- 8 files changed, 295 insertions(+), 161 deletions(-) delete mode 100644 src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorItem.java create mode 100644 src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java diff --git a/src/main/java/net/Chipperfluff/chipi/client/hud/ChipiStatusBar.java b/src/main/java/net/Chipperfluff/chipi/client/hud/ChipiStatusBar.java index 01c595a..e4c4546 100644 --- a/src/main/java/net/Chipperfluff/chipi/client/hud/ChipiStatusBar.java +++ b/src/main/java/net/Chipperfluff/chipi/client/hud/ChipiStatusBar.java @@ -14,6 +14,7 @@ public class ChipiStatusBar { private static final int ORANGE = 0xFFFF8000; private static final int OUTLINE_LIGHT = 0xFFB0B0B0; private static final int OUTLINE_DARK = 0xFF404040; + private static final int TEXT_COLOR = 0xFFFFFFFF; private static final int BAR_HEIGHT = 6; public static void register() { @@ -33,17 +34,33 @@ public class ChipiStatusBar { int screenWidth = client.getWindow().getScaledWidth(); int screenHeight = client.getWindow().getScaledHeight(); - int barWidth = (int)(80 * 0.8f); int centerX = screenWidth / 2; - int left = centerX - barWidth / 2; - int right = left + barWidth; - int heartsY = screenHeight - 39; - int barY = heartsY - BAR_HEIGHT - 4; + // ============================================================ + // EXACT vanilla hunger bar bounds (pixel-perfect) + // ============================================================ + int hungerRight = centerX + 91; + int hungerLeft = hungerRight - 81; // vanilla width (10 * 8 + 1) + int barWidth = hungerRight - hungerLeft; + + // Base hunger Y + int hungerY = screenHeight - 39; + + // While air bar is visible, hunger (and us) move up + if (client.player.getAir() < client.player.getMaxAir()) { + hungerY -= 10; + } + + // Our bar sits directly above hunger + int barY = hungerY - BAR_HEIGHT - 2; int fillWidth = (int)(barWidth * value); - float alpha = value >= 1f ? 0.65f : + // ============================================================ + // Alpha behavior + // ============================================================ + float alpha = + value >= 1f ? 0.65f : value < 0.10f ? (float)(0.6f + 0.4f * Math.sin(System.currentTimeMillis() / 120.0)) : 1f; @@ -51,19 +68,36 @@ public class ChipiStatusBar { int outlineDark = applyAlpha(OUTLINE_DARK, alpha); int outlineLight = applyAlpha(OUTLINE_LIGHT, alpha); int fillColor = applyAlpha(ORANGE, alpha); + int textColor = applyAlpha(TEXT_COLOR, alpha); - ctx.fill(left - 1, barY - 1, right + 1, barY, outlineDark); - ctx.fill(left - 1, barY + BAR_HEIGHT, right + 1, barY + BAR_HEIGHT + 1, outlineLight); - ctx.fill(left - 1, barY, left, barY + BAR_HEIGHT, outlineDark); - ctx.fill(right, barY, right + 1, barY + BAR_HEIGHT, outlineLight); - ctx.fill(left, barY, left + fillWidth, barY + BAR_HEIGHT, fillColor); + // ============================================================ + // Draw outline + // ============================================================ + ctx.fill(hungerLeft - 1, barY - 1, hungerRight + 1, barY, outlineDark); + ctx.fill(hungerLeft - 1, barY + BAR_HEIGHT, hungerRight + 1, barY + BAR_HEIGHT + 1, outlineLight); + ctx.fill(hungerLeft - 1, barY, hungerLeft, barY + BAR_HEIGHT, outlineDark); + ctx.fill(hungerRight, barY, hungerRight + 1, barY + BAR_HEIGHT, outlineLight); + + // ============================================================ + // Draw fill + // ============================================================ + ctx.fill(hungerLeft, barY, hungerLeft + fillWidth, barY + BAR_HEIGHT, fillColor); + + // ============================================================ + // Percent text INSIDE bar, centered + // ============================================================ + String text = String.format("%03d%%", Math.round(value * 100f)); + int textWidth = client.textRenderer.getWidth(text); + + int textX = hungerLeft + (barWidth - textWidth) / 2; + int textY = barY - 1; ctx.drawText( client.textRenderer, - String.format("%03d%%", Math.round(value * 100f)), - right + 6, - barY - 2, - fillColor, + text, + textX, + textY, + textColor, true ); } diff --git a/src/main/java/net/Chipperfluff/chipi/entity/MepEntity.java b/src/main/java/net/Chipperfluff/chipi/entity/MepEntity.java index ea4ac78..2b2cfb1 100644 --- a/src/main/java/net/Chipperfluff/chipi/entity/MepEntity.java +++ b/src/main/java/net/Chipperfluff/chipi/entity/MepEntity.java @@ -5,12 +5,7 @@ import net.minecraft.block.SlabBlock; import net.minecraft.block.StairsBlock; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.ai.goal.ActiveTargetGoal; -import net.minecraft.entity.ai.goal.LookAroundGoal; -import net.minecraft.entity.ai.goal.LookAtEntityGoal; -import net.minecraft.entity.ai.goal.MeleeAttackGoal; -import net.minecraft.entity.ai.goal.SwimGoal; -import net.minecraft.entity.ai.goal.WanderAroundFarGoal; +import net.minecraft.entity.ai.goal.*; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.damage.DamageSource; @@ -25,6 +20,7 @@ import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.Chipperfluff.chipi.armor.ProtectionAuraHandler; import net.Chipperfluff.chipi.item.ModItems; import net.Chipperfluff.chipi.sound.ModSounds; import net.Chipperfluff.chipi.util.TickScheduler; @@ -32,6 +28,7 @@ import net.Chipperfluff.chipi.util.TickScheduler; public class MepEntity extends PathAwareEntity { private static final int FORGET_TARGET_AFTER_TICKS = 100; + private static final int DESPAWN_DISTANCE = 300; private boolean angryAtPlayer = false; private int ticksSinceLastSeen = 0; @@ -56,7 +53,8 @@ public class MepEntity extends PathAwareEntity { this, PlayerEntity.class, true, - target -> target instanceof PlayerEntity player && !isPlayerProtected(player) + target -> target instanceof PlayerEntity player + && !isHardIgnored(player) ) ); } @@ -80,10 +78,28 @@ public class MepEntity extends PathAwareEntity { return super.damage(source, amount); } + // === ATTACK OVERRIDE (AURA IMMUNITY) === + + @Override + public boolean tryAttack(net.minecraft.entity.Entity target) { + if (target instanceof PlayerEntity player) { + if (ProtectionAuraHandler.hasAura(player)) { + return false; // chase but never hit + } + } + return super.tryAttack(target); + } + @Override public void tick() { super.tick(); + // --- DISTANCE DESPAWN ONLY --- + if (!this.getWorld().isClient && isTooFarFromAllPlayers()) { + this.discard(); + return; + } + LivingEntity target = this.getTarget(); if (!(target instanceof PlayerEntity player)) { @@ -92,7 +108,7 @@ public class MepEntity extends PathAwareEntity { return; } - if (isPlayerProtected(player)) { + if (isHardIgnored(player)) { clearTarget(); return; } @@ -118,7 +134,27 @@ public class MepEntity extends PathAwareEntity { this.getNavigation().stop(); } - private static boolean isPlayerProtected(PlayerEntity player) { + // === DISTANCE CHECK === + + private boolean isTooFarFromAllPlayers() { + double maxSq = DESPAWN_DISTANCE * DESPAWN_DISTANCE; + + for (PlayerEntity player : this.getWorld().getPlayers()) { + if (this.squaredDistanceTo(player) <= maxSq) { + return false; + } + } + return true; + } + + // === HARD IGNORE RULES === + + private static boolean isHardIgnored(PlayerEntity player) { + if (player.getZ() < 18) return true; + return isOnProtectedBlock(player); + } + + private static boolean isOnProtectedBlock(PlayerEntity player) { BlockPos pos = player.getBlockPos(); BlockState state = player.getWorld().getBlockState(pos); @@ -130,19 +166,7 @@ public class MepEntity extends PathAwareEntity { || state.getBlock() instanceof SlabBlock; } - // === Despawn prevention === - - @Override - public boolean cannotDespawn() { - return true; - } - - @Override - public boolean canImmediatelyDespawn(double distanceSquared) { - return false; - } - - // === MILKING (FEVER DREAM EDITION) === + // === MILKING === @Override public ActionResult interactMob(PlayerEntity player, Hand hand) { @@ -154,7 +178,6 @@ public class MepEntity extends PathAwareEntity { stack.decrement(1); player.giveItemStack(new ItemStack(ModItems.MEP_MILK)); - // ---- base sound ---- float basePitch = 0.3f + this.random.nextFloat() * 1.9f; float baseVolume = 0.9f + this.random.nextFloat() * 0.6f; @@ -167,10 +190,8 @@ public class MepEntity extends PathAwareEntity { basePitch ); - // ---- single delayed echo (10% chance) ---- if (this.random.nextFloat() < 0.10f) { - - int delay = 10 + this.random.nextInt(21); // 10–30 ticks + int delay = 10 + this.random.nextInt(21); float echoPitch = basePitch * 0.5f; float echoVolume = baseVolume * 0.5f; diff --git a/src/main/java/net/Chipperfluff/chipi/entity/SpawnLogic.java b/src/main/java/net/Chipperfluff/chipi/entity/SpawnLogic.java index 87580bf..b802018 100644 --- a/src/main/java/net/Chipperfluff/chipi/entity/SpawnLogic.java +++ b/src/main/java/net/Chipperfluff/chipi/entity/SpawnLogic.java @@ -18,9 +18,15 @@ import net.minecraft.util.math.random.Random; import net.minecraft.world.Heightmap; import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.biome.Biome; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.Box; + public final class SpawnLogic { + private static final int MAX_MEPS = 60; + private static final int SPAWN_RADIUS = 150; + private static final RegistryKey VOID_BIOME = RegistryKey.of(RegistryKeys.BIOME, new Identifier("chipi", "void")); @@ -46,16 +52,48 @@ public final class SpawnLogic { ); } - private static boolean canSpawn(EntityType type, ServerWorldAccess world, SpawnReason reason, BlockPos pos, Random random) { + private static boolean canSpawn( + EntityType type, + ServerWorldAccess world, + SpawnReason reason, + BlockPos pos, + Random random + ) { + // --- HEIGHT & Z RULES --- int y = pos.getY(); if (y < 87 || y > 90) return false; if (pos.getZ() < 18) return false; + // --- BLOCK CHECK --- BlockState below = world.getBlockState(pos.down()); - boolean can_spawn = below.isOf(Blocks.POLISHED_BLACKSTONE_BRICKS); + if (!below.isOf(Blocks.POLISHED_BLACKSTONE_BRICKS)) return false; - System.out.println("[MEP ENTITY] spawn check pos=" + pos + " below=" + below.getBlock()); + // --- GLOBAL CAP (DENY SPAWN, DO NOT DESPAWN) --- + int mepCount = world.getEntitiesByClass( + MepEntity.class, + new Box( + pos.getX() - 512, pos.getY() - 512, pos.getZ() - 512, + pos.getX() + 512, pos.getY() + 512, pos.getZ() + 512 + ), + e -> true + ).size(); - return can_spawn; + if (mepCount >= MAX_MEPS) return false; + + // --- PLAYER PROXIMITY --- + double maxSq = SPAWN_RADIUS * SPAWN_RADIUS; + + for (PlayerEntity player : world.getPlayers()) { + if (player.squaredDistanceTo( + pos.getX() + 0.5, + pos.getY(), + pos.getZ() + 0.5 + ) <= maxSq) { + return true; // ✔ valid spawn + } + } + + // No nearby player → no spawn + return false; } } diff --git a/src/main/java/net/Chipperfluff/chipi/item/ModItems.java b/src/main/java/net/Chipperfluff/chipi/item/ModItems.java index 6bf4ed6..a4e5ac3 100644 --- a/src/main/java/net/Chipperfluff/chipi/item/ModItems.java +++ b/src/main/java/net/Chipperfluff/chipi/item/ModItems.java @@ -11,8 +11,7 @@ import net.minecraft.registry.Registry; import net.minecraft.util.Identifier; import net.Chipperfluff.chipi.item.tool.ChipperToolMaterial; import net.Chipperfluff.chipi.item.MepMilkItem; -import net.minecraft.item.Items; -import net.Chipperfluff.chipi.item.armor.ChipperArmorItem; + public class ModItems { @@ -95,25 +94,25 @@ public class ModItems { public static final Item CHIPPER_HELMET = Registry.register( Registries.ITEM, new Identifier(ChipiMod.MOD_ID, "chipper_helmet"), - new ChipperArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.HELMET, new Item.Settings()) + new ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.HELMET, new Item.Settings()) ); public static final Item CHIPPER_CHESTPLATE = Registry.register( Registries.ITEM, new Identifier(ChipiMod.MOD_ID, "chipper_chestplate"), - new ChipperArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.CHESTPLATE, new Item.Settings()) + new ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.CHESTPLATE, new Item.Settings()) ); public static final Item CHIPPER_LEGGINGS = Registry.register( Registries.ITEM, new Identifier(ChipiMod.MOD_ID, "chipper_leggings"), - new ChipperArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.LEGGINGS, new Item.Settings()) + new ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.LEGGINGS, new Item.Settings()) ); public static final Item CHIPPER_BOOTS = Registry.register( Registries.ITEM, new Identifier(ChipiMod.MOD_ID, "chipper_boots"), - new ChipperArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.BOOTS, new Item.Settings()) + new ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.BOOTS, new Item.Settings()) ); // ===== TOOLS ===== diff --git a/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorItem.java b/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorItem.java deleted file mode 100644 index 7704948..0000000 --- a/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorItem.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.Chipperfluff.chipi.item.armor; - -import net.minecraft.entity.Entity; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.ArmorItem; -import net.minecraft.item.ArmorMaterial; -import net.minecraft.item.ItemStack; -import net.minecraft.world.World; - -public class ChipperArmorItem extends ArmorItem { - - public ChipperArmorItem( - ArmorMaterial material, - Type type, - Settings settings - ) { - super(material, type, settings); - } - - @Override - public void inventoryTick( - ItemStack stack, - World world, - Entity entity, - int slot, - boolean selected - ) { - if (slot >= EquipmentSlot.FEET.getEntitySlotId() - && slot <= EquipmentSlot.HEAD.getEntitySlotId()) { - - if (stack.getDamage() != 0) { - stack.setDamage(0); - } - } - } -} diff --git a/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorMaterial.java b/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorMaterial.java index d8ae7f9..65976cf 100644 --- a/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorMaterial.java +++ b/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorMaterial.java @@ -27,7 +27,7 @@ public class ChipperArmorMaterial implements ArmorMaterial { @Override public int getDurability(ArmorItem.Type type) { - return 10_000; + return 1_000; } @Override diff --git a/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java b/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java new file mode 100644 index 0000000..3b59af5 --- /dev/null +++ b/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java @@ -0,0 +1,135 @@ +package net.Chipperfluff.chipi.armor; + +import net.Chipperfluff.chipi.item.ModItems; +import net.minecraft.block.BlockState; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.StairsBlock; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import static net.Chipperfluff.chipi.util.ChipiTrackedData.CHIPI_ENERGY; +import static net.Chipperfluff.chipi.server.ChipiServerEvents.CHIPI_DIMENSION_KEY; + +public final class ProtectionAuraHandler { + + private static final float BASE_RECHARGE_RATE = 0.0005f; + private static final float BASE_DRAIN_RATE = 0.0008f; + + private ProtectionAuraHandler() {} + + /* ========================================================== + ENTRY POINT — CALLED SERVER SIDE + ========================================================== */ + + public static void tick(PlayerEntity player) { + if (!hasFullChipperArmor(player)) return; + + Float value = player.getDataTracker().get(CHIPI_ENERGY); + if (value == null) return; + + float durabilityFactor = getDurabilityFactor(player); + if (durabilityFactor <= 0f) return; + + boolean inChipi = isInChipi(player.getWorld()); + boolean protectedAura = isOnProtectedBlock(player); + + float rechargeRate = BASE_RECHARGE_RATE * durabilityFactor; + float drainRate = BASE_DRAIN_RATE * (1.0f + (1.0f - durabilityFactor)); + + float next = value; + + if (!inChipi || protectedAura) { + next = Math.min(1.0f, value + rechargeRate); + } else { + next = Math.max(0.0f, value - drainRate); + } + + if (next == value) return; + + // ───────────────────────────────────────────── + // PERCENT-BASED DURABILITY LOGIC + // ───────────────────────────────────────────── + int oldPercent = (int) Math.floor(value * 100f); + int newPercent = (int) Math.floor(next * 100f); + + int delta = Math.abs(newPercent - oldPercent); + + if (delta > 0) { + damageArmor(player, delta); + } + + player.getDataTracker().set(CHIPI_ENERGY, next); + } + + /* ========================================================== + ARMOR CHECK + ========================================================== */ + + public static boolean hasFullChipperArmor(PlayerEntity player) { + PlayerInventory inv = player.getInventory(); + return inv.getArmorStack(3).isOf(ModItems.CHIPPER_HELMET) + && inv.getArmorStack(2).isOf(ModItems.CHIPPER_CHESTPLATE) + && inv.getArmorStack(1).isOf(ModItems.CHIPPER_LEGGINGS) + && inv.getArmorStack(0).isOf(ModItems.CHIPPER_BOOTS); + } + + /* ========================================================== + DURABILITY LOGIC + ========================================================== */ + + private static float getDurabilityFactor(PlayerEntity player) { + int max = 0; + int current = 0; + + for (ItemStack stack : player.getArmorItems()) { + if (!stack.isEmpty()) { + max += stack.getMaxDamage(); + current += (stack.getMaxDamage() - stack.getDamage()); + } + } + + if (max <= 0) return 0f; + return Math.max(0f, Math.min(1f, (float) current / max)); + } + + private static void damageArmor(PlayerEntity player, int amount) { + for (ItemStack stack : player.getArmorItems()) { + if (!stack.isEmpty()) { + stack.damage(amount, player, p -> {}); + } + } + } + + /* ========================================================== + AURA CONDITIONS + ========================================================== */ + + private static boolean isOnProtectedBlock(PlayerEntity player) { + BlockPos pos = player.getBlockPos(); + BlockState state = player.getWorld().getBlockState(pos); + + if (!state.contains(Properties.WATERLOGGED) || !state.get(Properties.WATERLOGGED)) { + return false; + } + + return state.getBlock() instanceof StairsBlock + || state.getBlock() instanceof SlabBlock; + } + + private static boolean isInChipi(World world) { + return world.getRegistryKey().equals(CHIPI_DIMENSION_KEY); + } + + public static boolean hasAura(PlayerEntity player) { + if (!hasFullChipperArmor(player)) return false; + + Float value = player.getDataTracker().get(CHIPI_ENERGY); + if (value == null || value <= 0f) return false; + + return getDurabilityFactor(player) > 0f; + } +} diff --git a/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java b/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java index e116ef5..bc79f66 100644 --- a/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java +++ b/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java @@ -1,17 +1,13 @@ package net.Chipperfluff.chipi.server; import net.Chipperfluff.chipi.SpawnPlacedState; +import net.Chipperfluff.chipi.armor.ProtectionAuraHandler; import net.Chipperfluff.chipi.block.ChipperPortalBlock; -import net.Chipperfluff.chipi.item.ModItems; import net.Chipperfluff.chipi.world.gen.ChipiDungeonGenerator; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; -import net.minecraft.block.BlockState; -import net.minecraft.block.SlabBlock; -import net.minecraft.block.StairsBlock; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.entity.player.PlayerInventory; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; import net.minecraft.server.MinecraftServer; @@ -19,24 +15,18 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.structure.StructurePlacementData; import net.minecraft.structure.StructureTemplate; -import net.minecraft.state.property.Properties; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.world.GameRules; import net.minecraft.world.World; -import static net.Chipperfluff.chipi.util.ChipiTrackedData.CHIPI_ENERGY; - public final class ChipiServerEvents { public static final RegistryKey CHIPI_DIMENSION_KEY = - RegistryKey.of(RegistryKeys.WORLD, new Identifier("chipi", "chipi_dimension")); - - private static final float RECHARGE_RATE = 0.0005f; - private static final float DRAIN_RATE = 0.0008f; + RegistryKey.of(RegistryKeys.WORLD, new Identifier("chipi", "chipi_dimension")); private static final Identifier SPAWN_STRUCTURE = - new Identifier("chipi", "spawn"); + new Identifier("chipi", "spawn"); private static MinecraftServer SERVER; @@ -44,33 +34,14 @@ public final class ChipiServerEvents { public static void register() { ServerLifecycleEvents.SERVER_STARTED.register(server -> SERVER = server); - ServerTickEvents.END_SERVER_TICK.register(ChipiServerEvents::tickEnergy); + ServerTickEvents.END_SERVER_TICK.register(ChipiServerEvents::tickPlayers); ServerTickEvents.END_WORLD_TICK.register(ChipiServerEvents::handleVoidFailsafe); ServerWorldEvents.LOAD.register(ChipiServerEvents::onWorldLoad); } - private static void tickEnergy(MinecraftServer server) { + private static void tickPlayers(MinecraftServer server) { for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) { - - if (!hasFullChipperArmor(player)) continue; - - Float value = player.getDataTracker().get(CHIPI_ENERGY); - if (value == null) continue; // ultra-safe, but should never happen now - - boolean inChipi = player.getWorld().getRegistryKey().equals(CHIPI_DIMENSION_KEY); - boolean onProtected = isOnProtectedBlock(player); - - float next = value; - - if (!inChipi || onProtected) { - next = Math.min(1.0f, value + RECHARGE_RATE); - } else { - next = Math.max(0.0f, value - DRAIN_RATE); - } - - if (next != value) { - player.getDataTracker().set(CHIPI_ENERGY, next); - } + ProtectionAuraHandler.tick(player); } } @@ -91,31 +62,23 @@ public final class ChipiServerEvents { world.getGameRules().get(GameRules.DO_DAYLIGHT_CYCLE).set(false, server); SpawnPlacedState state = world.getPersistentStateManager().getOrCreate( - SpawnPlacedState::fromNbt, - SpawnPlacedState::new, - "chipi_spawn" + SpawnPlacedState::fromNbt, + SpawnPlacedState::new, + "chipi_spawn" ); if (state.placed) return; - StructureTemplate spawnTemplate = - world.getStructureTemplateManager().getTemplate(SPAWN_STRUCTURE).orElse(null); + StructureTemplate template = + world.getStructureTemplateManager().getTemplate(SPAWN_STRUCTURE).orElse(null); - if (spawnTemplate == null) return; + if (template == null) return; - BlockPos spawnCenter = new BlockPos(0, 80, 0); + BlockPos spawn = new BlockPos(0, 80, 0); - spawnTemplate.place( - world, - spawnCenter, - spawnCenter, - new StructurePlacementData(), - world.getRandom(), - 2 - ); - - world.setSpawnPos(spawnCenter.up(), 0.0f); - ChipiDungeonGenerator.generateInitialLayout(world, spawnCenter); + template.place(world, spawn, spawn, new StructurePlacementData(), world.getRandom(), 2); + world.setSpawnPos(spawn.up(), 0f); + ChipiDungeonGenerator.generateInitialLayout(world, spawn); state.placed = true; state.markDirty(); @@ -124,24 +87,4 @@ public final class ChipiServerEvents { public static ServerWorld getChipiWorld() { return SERVER == null ? null : SERVER.getWorld(CHIPI_DIMENSION_KEY); } - - private static boolean hasFullChipperArmor(PlayerEntity player) { - PlayerInventory inv = player.getInventory(); - return inv.getArmorStack(3).isOf(ModItems.CHIPPER_HELMET) - && inv.getArmorStack(2).isOf(ModItems.CHIPPER_CHESTPLATE) - && inv.getArmorStack(1).isOf(ModItems.CHIPPER_LEGGINGS) - && inv.getArmorStack(0).isOf(ModItems.CHIPPER_BOOTS); - } - - private static boolean isOnProtectedBlock(PlayerEntity player) { - BlockPos pos = player.getBlockPos(); - BlockState state = player.getWorld().getBlockState(pos); - - if (!state.contains(Properties.WATERLOGGED) || !state.get(Properties.WATERLOGGED)) { - return false; - } - - return state.getBlock() instanceof StairsBlock - || state.getBlock() instanceof SlabBlock; - } } From 4d35361b28203410496fd5f41c8ac2fabc9ad8d4 Mon Sep 17 00:00:00 2001 From: lordlogo2002 Date: Fri, 19 Dec 2025 23:47:22 +0100 Subject: [PATCH 2/3] Refactor ProtectionAuraHandler: streamline durability logic and add chat messages for armor status --- .../item/armor/ProtectionAuraHandler.java | 124 ++++++++++++++---- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java b/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java index 3b59af5..4f4ca7c 100644 --- a/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java +++ b/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java @@ -8,6 +8,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; import net.minecraft.state.property.Properties; +import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -19,10 +20,13 @@ public final class ProtectionAuraHandler { private static final float BASE_RECHARGE_RATE = 0.0005f; private static final float BASE_DRAIN_RATE = 0.0008f; + private static final float CASCADE_LOSS = 0.30f; // 30% relative + private static final int CRITICAL_PCT = 10; + private ProtectionAuraHandler() {} /* ========================================================== - ENTRY POINT — CALLED SERVER SIDE + ENTRY POINT — SERVER SIDE ========================================================== */ public static void tick(PlayerEntity player) { @@ -40,22 +44,14 @@ public final class ProtectionAuraHandler { float rechargeRate = BASE_RECHARGE_RATE * durabilityFactor; float drainRate = BASE_DRAIN_RATE * (1.0f + (1.0f - durabilityFactor)); - float next = value; - - if (!inChipi || protectedAura) { - next = Math.min(1.0f, value + rechargeRate); - } else { - next = Math.max(0.0f, value - drainRate); - } + float next = (!inChipi || protectedAura) + ? Math.min(1.0f, value + rechargeRate) + : Math.max(0.0f, value - drainRate); if (next == value) return; - // ───────────────────────────────────────────── - // PERCENT-BASED DURABILITY LOGIC - // ───────────────────────────────────────────── - int oldPercent = (int) Math.floor(value * 100f); - int newPercent = (int) Math.floor(next * 100f); - + int oldPercent = (int) (value * 100f); + int newPercent = (int) (next * 100f); int delta = Math.abs(newPercent - oldPercent); if (delta > 0) { @@ -82,26 +78,113 @@ public final class ProtectionAuraHandler { ========================================================== */ private static float getDurabilityFactor(PlayerEntity player) { - int max = 0; - int current = 0; + int max = 0, current = 0; for (ItemStack stack : player.getArmorItems()) { if (!stack.isEmpty()) { max += stack.getMaxDamage(); - current += (stack.getMaxDamage() - stack.getDamage()); + current += stack.getMaxDamage() - stack.getDamage(); } } if (max <= 0) return 0f; - return Math.max(0f, Math.min(1f, (float) current / max)); + return Math.min(1f, (float) current / max); } private static void damageArmor(PlayerEntity player, int amount) { + int[] before = getArmorPercents(player); + boolean broke = false; + for (ItemStack stack : player.getArmorItems()) { if (!stack.isEmpty()) { stack.damage(amount, player, p -> {}); + if (stack.getDamage() >= stack.getMaxDamage()) { + broke = true; + } } } + + if (broke) { + applyCascadeDamage(player, before); + } else { + sendWarningIfNeeded(player, before, getArmorPercents(player)); + } + } + + private static void applyCascadeDamage(PlayerEntity player, int[] before) { + for (ItemStack stack : player.getArmorItems()) { + if (stack.isEmpty()) continue; + + int max = stack.getMaxDamage(); + int current = max - stack.getDamage(); + if (current <= 0) continue; + + int extra = Math.round(current * CASCADE_LOSS); + stack.setDamage(Math.min(max, stack.getDamage() + extra)); + } + + player.getDataTracker().set(CHIPI_ENERGY, 0f); + sendCollapseMessage(player, before, getArmorPercents(player)); + } + + /* ========================================================== + CHAT MESSAGES (PLAYER ONLY) + ========================================================== */ + + private static void sendWarningIfNeeded(PlayerEntity player, int[] before, int[] after) { + boolean trigger = false; + for (int i = 0; i < 4; i++) { + if (before[i] > CRITICAL_PCT && after[i] <= CRITICAL_PCT) { + trigger = true; + } + } + if (!trigger) return; + + player.sendMessage(Text.literal(buildWarningMessage(after)), false); + } + + private static void sendCollapseMessage(PlayerEntity player, int[] before, int[] after) { + StringBuilder sb = new StringBuilder("§c[AURA COLLAPSE]\n"); + appendPiece(sb, "Head", before[3], after[3]); + appendPiece(sb, "Body", before[2], after[2]); + appendPiece(sb, "Legs", before[1], after[1]); + appendPiece(sb, "Feet", before[0], after[0]); + player.sendMessage(Text.literal(sb.toString()), false); + } + + private static String buildWarningMessage(int[] pct) { + StringBuilder sb = new StringBuilder("§6[AURA WARNING]\n"); + appendPiece(sb, "Head", pct[3]); + appendPiece(sb, "Body", pct[2]); + appendPiece(sb, "Legs", pct[1]); + appendPiece(sb, "Feet", pct[0]); + return sb.toString(); + } + + private static void appendPiece(StringBuilder sb, String name, int pct) { + sb.append("§7").append(name).append(": ").append(pct).append("%"); + if (pct <= CRITICAL_PCT) sb.append(" §c!!!"); + sb.append("\n"); + } + + private static void appendPiece(StringBuilder sb, String name, int before, int after) { + sb.append("§7").append(name).append(": ") + .append(after).append("% (was ").append(before).append("%)\n"); + } + + private static int[] getArmorPercents(PlayerEntity player) { + int[] out = new int[4]; + int i = 0; + for (ItemStack stack : player.getArmorItems()) { + if (stack.isEmpty()) { + out[i++] = 0; + } else { + int max = stack.getMaxDamage(); + int cur = max - stack.getDamage(); + out[i++] = Math.max(0, (int) ((cur / (float) max) * 100f)); + } + } + return out; } /* ========================================================== @@ -126,10 +209,7 @@ public final class ProtectionAuraHandler { public static boolean hasAura(PlayerEntity player) { if (!hasFullChipperArmor(player)) return false; - Float value = player.getDataTracker().get(CHIPI_ENERGY); - if (value == null || value <= 0f) return false; - - return getDurabilityFactor(player) > 0f; + return value != null && value > 0f && getDurabilityFactor(player) > 0f; } } From ed8c4433bee5bcdc6b33c8664f1d349bcade7794 Mon Sep 17 00:00:00 2001 From: lordlogo2002 Date: Fri, 19 Dec 2025 23:53:30 +0100 Subject: [PATCH 3/3] Refactor ProtectionAuraHandler: improve energy calculation and armor damage handling --- .../item/armor/ProtectionAuraHandler.java | 221 ++++++++++-------- 1 file changed, 123 insertions(+), 98 deletions(-) diff --git a/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java b/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java index 4f4ca7c..89be409 100644 --- a/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java +++ b/src/main/java/net/Chipperfluff/chipi/item/armor/ProtectionAuraHandler.java @@ -20,147 +20,149 @@ public final class ProtectionAuraHandler { private static final float BASE_RECHARGE_RATE = 0.0005f; private static final float BASE_DRAIN_RATE = 0.0008f; - private static final float CASCADE_LOSS = 0.30f; // 30% relative - private static final int CRITICAL_PCT = 10; + private static final float CASCADE_LOSS = 0.30f; + private static final int CRITICAL_PCT = 10; private ProtectionAuraHandler() {} /* ========================================================== - ENTRY POINT — SERVER SIDE + ENTRY POINT ========================================================== */ public static void tick(PlayerEntity player) { if (!hasFullChipperArmor(player)) return; - Float value = player.getDataTracker().get(CHIPI_ENERGY); - if (value == null) return; + Float energy = player.getDataTracker().get(CHIPI_ENERGY); + if (energy == null) return; float durabilityFactor = getDurabilityFactor(player); if (durabilityFactor <= 0f) return; - boolean inChipi = isInChipi(player.getWorld()); - boolean protectedAura = isOnProtectedBlock(player); + float nextEnergy = calculateNextEnergy(player, energy, durabilityFactor); + if (nextEnergy == energy) return; - float rechargeRate = BASE_RECHARGE_RATE * durabilityFactor; - float drainRate = BASE_DRAIN_RATE * (1.0f + (1.0f - durabilityFactor)); + applyEnergyDelta(player, energy, nextEnergy); + player.getDataTracker().set(CHIPI_ENERGY, nextEnergy); + } - float next = (!inChipi || protectedAura) - ? Math.min(1.0f, value + rechargeRate) - : Math.max(0.0f, value - drainRate); + /* ========================================================== + ENERGY + ========================================================== */ - if (next == value) return; + private static float calculateNextEnergy(PlayerEntity player, float current, float durabilityFactor) { + boolean recharge = !isInChipi(player.getWorld()) || isOnProtectedBlock(player); - int oldPercent = (int) (value * 100f); - int newPercent = (int) (next * 100f); - int delta = Math.abs(newPercent - oldPercent); + float rate = recharge + ? BASE_RECHARGE_RATE * durabilityFactor + : BASE_DRAIN_RATE * (1.0f + (1.0f - durabilityFactor)); + + return recharge + ? Math.min(1.0f, current + rate) + : Math.max(0.0f, current - rate); + } + + private static void applyEnergyDelta(PlayerEntity player, float before, float after) { + int oldPct = (int) (before * 100f); + int newPct = (int) (after * 100f); + int delta = Math.abs(newPct - oldPct); if (delta > 0) { - damageArmor(player, delta); + applyArmorDamage(player, delta); } - - player.getDataTracker().set(CHIPI_ENERGY, next); } /* ========================================================== - ARMOR CHECK + ARMOR DAMAGE ========================================================== */ - public static boolean hasFullChipperArmor(PlayerEntity player) { - PlayerInventory inv = player.getInventory(); - return inv.getArmorStack(3).isOf(ModItems.CHIPPER_HELMET) - && inv.getArmorStack(2).isOf(ModItems.CHIPPER_CHESTPLATE) - && inv.getArmorStack(1).isOf(ModItems.CHIPPER_LEGGINGS) - && inv.getArmorStack(0).isOf(ModItems.CHIPPER_BOOTS); - } - - /* ========================================================== - DURABILITY LOGIC - ========================================================== */ - - private static float getDurabilityFactor(PlayerEntity player) { - int max = 0, current = 0; - - for (ItemStack stack : player.getArmorItems()) { - if (!stack.isEmpty()) { - max += stack.getMaxDamage(); - current += stack.getMaxDamage() - stack.getDamage(); - } - } - - if (max <= 0) return 0f; - return Math.min(1f, (float) current / max); - } - - private static void damageArmor(PlayerEntity player, int amount) { + private static void applyArmorDamage(PlayerEntity player, int amount) { int[] before = getArmorPercents(player); + boolean broke = damageAllPieces(player, amount); + + if (broke) { + handleCascade(player, before); + } else { + handleWarnings(player, before, getArmorPercents(player)); + } + } + + private static boolean damageAllPieces(PlayerEntity player, int amount) { boolean broke = false; - for (ItemStack stack : player.getArmorItems()) { - if (!stack.isEmpty()) { - stack.damage(amount, player, p -> {}); - if (stack.getDamage() >= stack.getMaxDamage()) { - broke = true; - } - } - } - - if (broke) { - applyCascadeDamage(player, before); - } else { - sendWarningIfNeeded(player, before, getArmorPercents(player)); - } - } - - private static void applyCascadeDamage(PlayerEntity player, int[] before) { for (ItemStack stack : player.getArmorItems()) { if (stack.isEmpty()) continue; - int max = stack.getMaxDamage(); - int current = max - stack.getDamage(); - if (current <= 0) continue; - - int extra = Math.round(current * CASCADE_LOSS); - stack.setDamage(Math.min(max, stack.getDamage() + extra)); + stack.damage(amount, player, p -> {}); + if (stack.getDamage() >= stack.getMaxDamage()) { + broke = true; + } } + return broke; + } + /* ========================================================== + CASCADE + ========================================================== */ + + private static void handleCascade(PlayerEntity player, int[] before) { + applyCascadeLoss(player); player.getDataTracker().set(CHIPI_ENERGY, 0f); sendCollapseMessage(player, before, getArmorPercents(player)); } + private static void applyCascadeLoss(PlayerEntity player) { + for (ItemStack stack : player.getArmorItems()) { + if (stack.isEmpty()) continue; + + int max = stack.getMaxDamage(); + int remaining = max - stack.getDamage(); + if (remaining <= 0) continue; + + int extra = Math.round(remaining * CASCADE_LOSS); + stack.setDamage(Math.min(max, stack.getDamage() + extra)); + } + } + /* ========================================================== - CHAT MESSAGES (PLAYER ONLY) + WARNINGS ========================================================== */ - private static void sendWarningIfNeeded(PlayerEntity player, int[] before, int[] after) { - boolean trigger = false; + private static void handleWarnings(PlayerEntity player, int[] before, int[] after) { + if (!enteredCritical(before, after)) return; + player.sendMessage(Text.literal(buildWarningMessage(after)), false); + } + + private static boolean enteredCritical(int[] before, int[] after) { for (int i = 0; i < 4; i++) { if (before[i] > CRITICAL_PCT && after[i] <= CRITICAL_PCT) { - trigger = true; + return true; } } - if (!trigger) return; + return false; + } - player.sendMessage(Text.literal(buildWarningMessage(after)), false); + /* ========================================================== + CHAT BUILDERS + ========================================================== */ + + private static String buildWarningMessage(int[] pct) { + StringBuilder sb = new StringBuilder("§6[AURA WARNING]\n"); + appendPiece(sb, "Head", pct[3]); + appendPiece(sb, "Body", pct[2]); + appendPiece(sb, "Legs", pct[1]); + appendPiece(sb, "Feet", pct[0]); + return sb.toString(); } private static void sendCollapseMessage(PlayerEntity player, int[] before, int[] after) { StringBuilder sb = new StringBuilder("§c[AURA COLLAPSE]\n"); - appendPiece(sb, "Head", before[3], after[3]); - appendPiece(sb, "Body", before[2], after[2]); - appendPiece(sb, "Legs", before[1], after[1]); - appendPiece(sb, "Feet", before[0], after[0]); + appendPiece(sb, "Head", before[3], after[3]); + appendPiece(sb, "Body", before[2], after[2]); + appendPiece(sb, "Legs", before[1], after[1]); + appendPiece(sb, "Feet", before[0], after[0]); player.sendMessage(Text.literal(sb.toString()), false); } - private static String buildWarningMessage(int[] pct) { - StringBuilder sb = new StringBuilder("§6[AURA WARNING]\n"); - appendPiece(sb, "Head", pct[3]); - appendPiece(sb, "Body", pct[2]); - appendPiece(sb, "Legs", pct[1]); - appendPiece(sb, "Feet", pct[0]); - return sb.toString(); - } - private static void appendPiece(StringBuilder sb, String name, int pct) { sb.append("§7").append(name).append(": ").append(pct).append("%"); if (pct <= CRITICAL_PCT) sb.append(" §c!!!"); @@ -172,9 +174,14 @@ public final class ProtectionAuraHandler { .append(after).append("% (was ").append(before).append("%)\n"); } + /* ========================================================== + UTIL + ========================================================== */ + private static int[] getArmorPercents(PlayerEntity player) { int[] out = new int[4]; int i = 0; + for (ItemStack stack : player.getArmorItems()) { if (stack.isEmpty()) { out[i++] = 0; @@ -187,20 +194,38 @@ public final class ProtectionAuraHandler { return out; } + private static float getDurabilityFactor(PlayerEntity player) { + int max = 0, cur = 0; + + for (ItemStack stack : player.getArmorItems()) { + if (!stack.isEmpty()) { + max += stack.getMaxDamage(); + cur += stack.getMaxDamage() - stack.getDamage(); + } + } + return max <= 0 ? 0f : Math.min(1f, (float) cur / max); + } + /* ========================================================== - AURA CONDITIONS + CONDITIONS ========================================================== */ + public static boolean hasFullChipperArmor(PlayerEntity player) { + PlayerInventory inv = player.getInventory(); + return inv.getArmorStack(3).isOf(ModItems.CHIPPER_HELMET) + && inv.getArmorStack(2).isOf(ModItems.CHIPPER_CHESTPLATE) + && inv.getArmorStack(1).isOf(ModItems.CHIPPER_LEGGINGS) + && inv.getArmorStack(0).isOf(ModItems.CHIPPER_BOOTS); + } + private static boolean isOnProtectedBlock(PlayerEntity player) { BlockPos pos = player.getBlockPos(); BlockState state = player.getWorld().getBlockState(pos); - if (!state.contains(Properties.WATERLOGGED) || !state.get(Properties.WATERLOGGED)) { - return false; - } - - return state.getBlock() instanceof StairsBlock - || state.getBlock() instanceof SlabBlock; + return state.contains(Properties.WATERLOGGED) + && state.get(Properties.WATERLOGGED) + && (state.getBlock() instanceof StairsBlock + || state.getBlock() instanceof SlabBlock); } private static boolean isInChipi(World world) { @@ -209,7 +234,7 @@ public final class ProtectionAuraHandler { public static boolean hasAura(PlayerEntity player) { if (!hasFullChipperArmor(player)) return false; - Float value = player.getDataTracker().get(CHIPI_ENERGY); - return value != null && value > 0f && getDurabilityFactor(player) > 0f; + Float v = player.getDataTracker().get(CHIPI_ENERGY); + return v != null && v > 0f && getDurabilityFactor(player) > 0f; } }