diff --git a/src/main/java/net/Chipperfluff/chipi/client/ChipiClient.java b/src/main/java/net/Chipperfluff/chipi/client/ChipiClient.java index 3a7984c..a64e972 100644 --- a/src/main/java/net/Chipperfluff/chipi/client/ChipiClient.java +++ b/src/main/java/net/Chipperfluff/chipi/client/ChipiClient.java @@ -2,6 +2,8 @@ package net.Chipperfluff.chipi.client; import net.Chipperfluff.chipi.block.ModBlocks; import net.Chipperfluff.chipi.client.entity.ModEntityRenderers; +import net.Chipperfluff.chipi.client.hud.ChipiStatusBar; +import net.Chipperfluff.chipi.util.ClientTickScheduler; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; import net.minecraft.client.render.RenderLayer; @@ -13,5 +15,7 @@ public class ChipiClient implements ClientModInitializer { BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.CHIPPER_PORTAL, RenderLayer.getTranslucent()); ModEntityRenderers.register(); ModTooltips.register(); + ChipiStatusBar.register(); + ClientTickScheduler.init(); } } diff --git a/src/main/java/net/Chipperfluff/chipi/client/hud/ChipiStatusBar.java b/src/main/java/net/Chipperfluff/chipi/client/hud/ChipiStatusBar.java new file mode 100644 index 0000000..01c595a --- /dev/null +++ b/src/main/java/net/Chipperfluff/chipi/client/hud/ChipiStatusBar.java @@ -0,0 +1,83 @@ +package net.Chipperfluff.chipi.client.hud; + +import net.Chipperfluff.chipi.item.ModItems; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; + +import static net.Chipperfluff.chipi.util.ChipiTrackedData.CHIPI_ENERGY; + +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 BAR_HEIGHT = 6; + + public static void register() { + HudRenderCallback.EVENT.register(ChipiStatusBar::render); + } + + private static void render(DrawContext ctx, float tickDelta) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null || client.options.hudHidden) return; + if (!hasFullChipperArmor(client.player)) return; + + Float value = client.player.getDataTracker().get(CHIPI_ENERGY); + if (value == null) return; + + value = Math.max(0f, Math.min(1f, value)); + + 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; + + int fillWidth = (int)(barWidth * value); + + float alpha = value >= 1f ? 0.65f : + value < 0.10f + ? (float)(0.6f + 0.4f * Math.sin(System.currentTimeMillis() / 120.0)) + : 1f; + + int outlineDark = applyAlpha(OUTLINE_DARK, alpha); + int outlineLight = applyAlpha(OUTLINE_LIGHT, alpha); + int fillColor = applyAlpha(ORANGE, 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); + + ctx.drawText( + client.textRenderer, + String.format("%03d%%", Math.round(value * 100f)), + right + 6, + barY - 2, + fillColor, + true + ); + } + + private static int applyAlpha(int color, float alpha) { + int a = (int)(((color >> 24) & 0xFF) * alpha); + return (a << 24) | (color & 0x00FFFFFF); + } + + 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); + } +} diff --git a/src/main/java/net/Chipperfluff/chipi/item/ModItems.java b/src/main/java/net/Chipperfluff/chipi/item/ModItems.java index 09d47be..6bf4ed6 100644 --- a/src/main/java/net/Chipperfluff/chipi/item/ModItems.java +++ b/src/main/java/net/Chipperfluff/chipi/item/ModItems.java @@ -12,6 +12,7 @@ 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 { @@ -94,25 +95,25 @@ public class ModItems { public static final Item CHIPPER_HELMET = Registry.register( Registries.ITEM, new Identifier(ChipiMod.MOD_ID, "chipper_helmet"), - new ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.HELMET, new Item.Settings()) + new ChipperArmorItem(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 ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.CHESTPLATE, new Item.Settings()) + new ChipperArmorItem(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 ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.LEGGINGS, new Item.Settings()) + new ChipperArmorItem(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 ArmorItem(ChipperArmorMaterial.INSTANCE, ArmorItem.Type.BOOTS, new Item.Settings()) + new ChipperArmorItem(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 new file mode 100644 index 0000000..7704948 --- /dev/null +++ b/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorItem.java @@ -0,0 +1,36 @@ +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 46c5680..d8ae7f9 100644 --- a/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorMaterial.java +++ b/src/main/java/net/Chipperfluff/chipi/item/armor/ChipperArmorMaterial.java @@ -18,16 +18,16 @@ public class ChipperArmorMaterial implements ArmorMaterial { private static final Map PROTECTION = Util.make( new EnumMap<>(ArmorItem.Type.class), map -> { - map.put(ArmorItem.Type.HELMET, 2); - map.put(ArmorItem.Type.CHESTPLATE, 5); - map.put(ArmorItem.Type.LEGGINGS, 4); - map.put(ArmorItem.Type.BOOTS, 2); + map.put(ArmorItem.Type.HELMET, 0); + map.put(ArmorItem.Type.CHESTPLATE, 0); + map.put(ArmorItem.Type.LEGGINGS, 0); + map.put(ArmorItem.Type.BOOTS, 0); } ); @Override public int getDurability(ArmorItem.Type type) { - return 3 * type.getEquipmentSlot().getEntitySlotId(); + return 10_000; } @Override @@ -57,11 +57,11 @@ public class ChipperArmorMaterial implements ArmorMaterial { @Override public float getToughness() { - return 2.0f; + return 0.0f; } @Override public float getKnockbackResistance() { - return 0.4f; + return 0.0f; } } diff --git a/src/main/java/net/Chipperfluff/chipi/mixin/DataTrackerMixin.java b/src/main/java/net/Chipperfluff/chipi/mixin/DataTrackerMixin.java new file mode 100644 index 0000000..8021813 --- /dev/null +++ b/src/main/java/net/Chipperfluff/chipi/mixin/DataTrackerMixin.java @@ -0,0 +1,29 @@ +package net.Chipperfluff.chipi.mixin; + +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(DataTracker.class) +public abstract class DataTrackerMixin { + + @Shadow + protected abstract DataTracker.Entry getEntry(TrackedData data); + + @Inject(method = "get", at = @At("HEAD"), cancellable = true) + private void chipi$nullSafeGet( + TrackedData data, + CallbackInfoReturnable cir + ) { + DataTracker.Entry entry = this.getEntry(data); + + if (entry == null) { + cir.setReturnValue(null); + } + } +} diff --git a/src/main/java/net/Chipperfluff/chipi/mixin/PlayerEntityMixin.java b/src/main/java/net/Chipperfluff/chipi/mixin/PlayerEntityMixin.java new file mode 100644 index 0000000..65134e9 --- /dev/null +++ b/src/main/java/net/Chipperfluff/chipi/mixin/PlayerEntityMixin.java @@ -0,0 +1,30 @@ +package net.Chipperfluff.chipi.mixin; + +import com.mojang.authlib.GameProfile; +import net.Chipperfluff.chipi.util.ChipiTrackedData; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +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.CallbackInfo; + +@Mixin(PlayerEntity.class) +public abstract class PlayerEntityMixin { + + @Inject(method = "", at = @At("TAIL")) + private void chipi$initTrackedData( + World world, + BlockPos pos, + float yaw, + GameProfile profile, + CallbackInfo ci + ) { + PlayerEntity self = (PlayerEntity)(Object)this; + DataTracker tracker = self.getDataTracker(); + + tracker.startTracking(ChipiTrackedData.CHIPI_ENERGY, 1.0f); + } +} diff --git a/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java b/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java index 1b10c1f..e116ef5 100644 --- a/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java +++ b/src/main/java/net/Chipperfluff/chipi/server/ChipiServerEvents.java @@ -1,115 +1,147 @@ package net.Chipperfluff.chipi.server; -import net.Chipperfluff.chipi.block.ChipperPortalBlock; -import net.Chipperfluff.chipi.world.gen.ChipiDungeonGenerator; import net.Chipperfluff.chipi.SpawnPlacedState; - +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; +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")); + 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; private static final Identifier SPAWN_STRUCTURE = - new Identifier("chipi", "spawn"); + new Identifier("chipi", "spawn"); private static MinecraftServer SERVER; private ChipiServerEvents() {} public static void register() { - - ServerLifecycleEvents.SERVER_STARTED.register(server -> { - SERVER = server; - }); - + ServerLifecycleEvents.SERVER_STARTED.register(server -> SERVER = server); + ServerTickEvents.END_SERVER_TICK.register(ChipiServerEvents::tickEnergy); ServerTickEvents.END_WORLD_TICK.register(ChipiServerEvents::handleVoidFailsafe); - ServerWorldEvents.LOAD.register(ChipiServerEvents::onWorldLoad); } - // ============================================================ - // Tick logic - // ============================================================ + private static void tickEnergy(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); + } + } + } + private static void handleVoidFailsafe(ServerWorld world) { if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) return; for (PlayerEntity player : world.getPlayers()) { - if (player.getBlockY() >= 50) continue; - ChipperPortalBlock.teleportToChipiSpawn(world, player); + if (player.getBlockY() < 50) { + ChipperPortalBlock.teleportToChipiSpawn(world, player); + } } } - // ============================================================ - // World init - // ============================================================ private static void onWorldLoad(MinecraftServer server, ServerWorld world) { if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) return; world.setTimeOfDay(18000); - world.getGameRules() - .get(GameRules.DO_DAYLIGHT_CYCLE) - .set(false, server); + 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); + world.getStructureTemplateManager().getTemplate(SPAWN_STRUCTURE).orElse(null); - if (spawnTemplate == null) { - System.err.println("[CHIPI] spawn.nbt not found!"); - return; - } + if (spawnTemplate == null) return; BlockPos spawnCenter = new BlockPos(0, 80, 0); spawnTemplate.place( - world, - spawnCenter, - spawnCenter, - new StructurePlacementData(), - world.getRandom(), - 2 + world, + spawnCenter, + spawnCenter, + new StructurePlacementData(), + world.getRandom(), + 2 ); world.setSpawnPos(spawnCenter.up(), 0.0f); - ChipiDungeonGenerator.generateInitialLayout(world, spawnCenter); state.placed = true; state.markDirty(); - - System.out.println("[CHIPI] Spawn + initial dungeon generated"); } - // ============================================================ - // Access - // ============================================================ public static ServerWorld getChipiWorld() { - if (SERVER == null) return null; - return SERVER.getWorld(CHIPI_DIMENSION_KEY); + 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; } } diff --git a/src/main/java/net/Chipperfluff/chipi/util/ChipiTrackedData.java b/src/main/java/net/Chipperfluff/chipi/util/ChipiTrackedData.java new file mode 100644 index 0000000..d4dd3ad --- /dev/null +++ b/src/main/java/net/Chipperfluff/chipi/util/ChipiTrackedData.java @@ -0,0 +1,14 @@ +package net.Chipperfluff.chipi.util; + +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.player.PlayerEntity; + +public final class ChipiTrackedData { + + public static final TrackedData CHIPI_ENERGY = + DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT); + + private ChipiTrackedData() {} +} diff --git a/src/main/java/net/Chipperfluff/chipi/util/ClientTickScheduler.java b/src/main/java/net/Chipperfluff/chipi/util/ClientTickScheduler.java new file mode 100644 index 0000000..96e1c07 --- /dev/null +++ b/src/main/java/net/Chipperfluff/chipi/util/ClientTickScheduler.java @@ -0,0 +1,52 @@ +package net.Chipperfluff.chipi.util; + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.minecraft.client.MinecraftClient; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class ClientTickScheduler { + + private static final List TASKS = new LinkedList<>(); + private static boolean registered = false; + + public static void init() { + if (registered) return; + registered = true; + + ClientTickEvents.END_CLIENT_TICK.register(ClientTickScheduler::tick); + } + + public static void schedule(int delayTicks, Runnable action) { + TASKS.add(new ScheduledTask(delayTicks, action)); + } + + private static void tick(MinecraftClient client) { + Iterator it = TASKS.iterator(); + while (it.hasNext()) { + ScheduledTask task = it.next(); + task.ticks--; + + if (task.ticks <= 0) { + try { + task.action.run(); + } catch (Exception e) { + e.printStackTrace(); + } + it.remove(); + } + } + } + + private static class ScheduledTask { + int ticks; + Runnable action; + + ScheduledTask(int ticks, Runnable action) { + this.ticks = ticks; + this.action = action; + } + } +} diff --git a/src/main/resources/chipi.mixins.json b/src/main/resources/chipi.mixins.json index f5b6956..2a584ea 100644 --- a/src/main/resources/chipi.mixins.json +++ b/src/main/resources/chipi.mixins.json @@ -4,6 +4,8 @@ "package": "net.Chipperfluff.chipi.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ + "DataTrackerMixin", + "PlayerEntityMixin" ], "injectors": { "defaultRequire": 1