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.block.ModBlocks;
import net.Chipperfluff.chipi.client.entity.ModEntityRenderers; 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.api.ClientModInitializer;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayer;
@ -13,5 +15,7 @@ public class ChipiClient implements ClientModInitializer {
BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.CHIPPER_PORTAL, RenderLayer.getTranslucent()); BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.CHIPPER_PORTAL, RenderLayer.getTranslucent());
ModEntityRenderers.register(); ModEntityRenderers.register();
ModTooltips.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.tool.ChipperToolMaterial;
import net.Chipperfluff.chipi.item.MepMilkItem; import net.Chipperfluff.chipi.item.MepMilkItem;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.Chipperfluff.chipi.item.armor.ChipperArmorItem;
public class ModItems { public class ModItems {
@ -94,25 +95,25 @@ public class ModItems {
public static final Item CHIPPER_HELMET = Registry.register( public static final Item CHIPPER_HELMET = Registry.register(
Registries.ITEM, Registries.ITEM,
new Identifier(ChipiMod.MOD_ID, "chipper_helmet"), 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( public static final Item CHIPPER_CHESTPLATE = Registry.register(
Registries.ITEM, Registries.ITEM,
new Identifier(ChipiMod.MOD_ID, "chipper_chestplate"), 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( public static final Item CHIPPER_LEGGINGS = Registry.register(
Registries.ITEM, Registries.ITEM,
new Identifier(ChipiMod.MOD_ID, "chipper_leggings"), 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( public static final Item CHIPPER_BOOTS = Registry.register(
Registries.ITEM, Registries.ITEM,
new Identifier(ChipiMod.MOD_ID, "chipper_boots"), 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 ===== // ===== 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( private static final Map<ArmorItem.Type, Integer> PROTECTION = Util.make(
new EnumMap<>(ArmorItem.Type.class), new EnumMap<>(ArmorItem.Type.class),
map -> { map -> {
map.put(ArmorItem.Type.HELMET, 2); map.put(ArmorItem.Type.HELMET, 0);
map.put(ArmorItem.Type.CHESTPLATE, 5); map.put(ArmorItem.Type.CHESTPLATE, 0);
map.put(ArmorItem.Type.LEGGINGS, 4); map.put(ArmorItem.Type.LEGGINGS, 0);
map.put(ArmorItem.Type.BOOTS, 2); map.put(ArmorItem.Type.BOOTS, 0);
} }
); );
@Override @Override
public int getDurability(ArmorItem.Type type) { public int getDurability(ArmorItem.Type type) {
return 3 * type.getEquipmentSlot().getEntitySlotId(); return 10_000;
} }
@Override @Override
@ -57,11 +57,11 @@ public class ChipperArmorMaterial implements ArmorMaterial {
@Override @Override
public float getToughness() { public float getToughness() {
return 2.0f; return 0.0f;
} }
@Override @Override
public float getKnockbackResistance() { 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,30 +1,40 @@
package net.Chipperfluff.chipi.server; 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.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.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; 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.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.structure.StructurePlacementData; import net.minecraft.structure.StructurePlacementData;
import net.minecraft.structure.StructureTemplate; import net.minecraft.structure.StructureTemplate;
import net.minecraft.state.property.Properties;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameRules; import net.minecraft.world.GameRules;
import net.minecraft.world.World; import net.minecraft.world.World;
import static net.Chipperfluff.chipi.util.ChipiTrackedData.CHIPI_ENERGY;
public final class ChipiServerEvents { public final class ChipiServerEvents {
public static final RegistryKey<World> CHIPI_DIMENSION_KEY = 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 = private static final Identifier SPAWN_STRUCTURE =
new Identifier("chipi", "spawn"); new Identifier("chipi", "spawn");
@ -33,38 +43,52 @@ public final class ChipiServerEvents {
private ChipiServerEvents() {} private ChipiServerEvents() {}
public static void register() { public static void register() {
ServerLifecycleEvents.SERVER_STARTED.register(server -> SERVER = server);
ServerLifecycleEvents.SERVER_STARTED.register(server -> { ServerTickEvents.END_SERVER_TICK.register(ChipiServerEvents::tickEnergy);
SERVER = server;
});
ServerTickEvents.END_WORLD_TICK.register(ChipiServerEvents::handleVoidFailsafe); ServerTickEvents.END_WORLD_TICK.register(ChipiServerEvents::handleVoidFailsafe);
ServerWorldEvents.LOAD.register(ChipiServerEvents::onWorldLoad); ServerWorldEvents.LOAD.register(ChipiServerEvents::onWorldLoad);
} }
// ============================================================ private static void tickEnergy(MinecraftServer server) {
// Tick logic 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) { private static void handleVoidFailsafe(ServerWorld world) {
if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) return; if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) return;
for (PlayerEntity player : world.getPlayers()) { for (PlayerEntity player : world.getPlayers()) {
if (player.getBlockY() >= 50) continue; if (player.getBlockY() < 50) {
ChipperPortalBlock.teleportToChipiSpawn(world, player); ChipperPortalBlock.teleportToChipiSpawn(world, player);
} }
} }
}
// ============================================================
// World init
// ============================================================
private static void onWorldLoad(MinecraftServer server, ServerWorld world) { private static void onWorldLoad(MinecraftServer server, ServerWorld world) {
if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) return; if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) return;
world.setTimeOfDay(18000); world.setTimeOfDay(18000);
world.getGameRules() world.getGameRules().get(GameRules.DO_DAYLIGHT_CYCLE).set(false, server);
.get(GameRules.DO_DAYLIGHT_CYCLE)
.set(false, server);
SpawnPlacedState state = world.getPersistentStateManager().getOrCreate( SpawnPlacedState state = world.getPersistentStateManager().getOrCreate(
SpawnPlacedState::fromNbt, SpawnPlacedState::fromNbt,
@ -75,14 +99,9 @@ public final class ChipiServerEvents {
if (state.placed) return; if (state.placed) return;
StructureTemplate spawnTemplate = StructureTemplate spawnTemplate =
world.getStructureTemplateManager() world.getStructureTemplateManager().getTemplate(SPAWN_STRUCTURE).orElse(null);
.getTemplate(SPAWN_STRUCTURE)
.orElse(null);
if (spawnTemplate == null) { if (spawnTemplate == null) return;
System.err.println("[CHIPI] spawn.nbt not found!");
return;
}
BlockPos spawnCenter = new BlockPos(0, 80, 0); BlockPos spawnCenter = new BlockPos(0, 80, 0);
@ -96,20 +115,33 @@ public final class ChipiServerEvents {
); );
world.setSpawnPos(spawnCenter.up(), 0.0f); world.setSpawnPos(spawnCenter.up(), 0.0f);
ChipiDungeonGenerator.generateInitialLayout(world, spawnCenter); ChipiDungeonGenerator.generateInitialLayout(world, spawnCenter);
state.placed = true; state.placed = true;
state.markDirty(); state.markDirty();
System.out.println("[CHIPI] Spawn + initial dungeon generated");
} }
// ============================================================
// Access
// ============================================================
public static ServerWorld getChipiWorld() { public static ServerWorld getChipiWorld() {
if (SERVER == null) return null; return SERVER == null ? null : SERVER.getWorld(CHIPI_DIMENSION_KEY);
return 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", "package": "net.Chipperfluff.chipi.mixin",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"mixins": [ "mixins": [
"DataTrackerMixin",
"PlayerEntityMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1