This commit is contained in:
Chipperfluff 2025-12-19 21:30:27 +01:00
parent fc78faffb5
commit e3166bbf1a
11 changed files with 341 additions and 58 deletions

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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 =====

View File

@ -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);
}
}
}
}

View File

@ -18,16 +18,16 @@ public class ChipperArmorMaterial implements ArmorMaterial {
private static final Map<ArmorItem.Type, Integer> 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;
}
}

View File

@ -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 <T> DataTracker.Entry<T> getEntry(TrackedData<T> data);
@Inject(method = "get", at = @At("HEAD"), cancellable = true)
private <T> void chipi$nullSafeGet(
TrackedData<T> data,
CallbackInfoReturnable<T> cir
) {
DataTracker.Entry<T> entry = this.getEntry(data);
if (entry == null) {
cir.setReturnValue(null);
}
}
}

View File

@ -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 = "<init>", 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);
}
}

View File

@ -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<World> 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;
}
}

View File

@ -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<Float> CHIPI_ENERGY =
DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
private ChipiTrackedData() {}
}

View File

@ -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<ScheduledTask> 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<ScheduledTask> 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;
}
}
}

View File

@ -4,6 +4,8 @@
"package": "net.Chipperfluff.chipi.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"DataTrackerMixin",
"PlayerEntityMixin"
],
"injectors": {
"defaultRequire": 1