chore: initial commit

This commit is contained in:
2026-02-15 10:50:17 +01:00
commit 055b93817a
78 changed files with 23243 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
package com.clod.clod;
import com.clod.clod.client.ClodEntityRenderer;
import com.clod.clod.client.ClodModel;
import com.clod.clod.command.ClodCommands;
import com.clod.clod.entity.ClodEntity;
import com.clod.clod.init.ModEntities;
import com.clod.clod.server.ClodHttpServer;
import com.mojang.logging.LogUtils;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.event.ServerChatEvent;
import net.neoforged.neoforge.event.entity.EntityAttributeCreationEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import org.slf4j.Logger;
@Mod(Clod.MODID)
public class Clod {
public static final String MODID = "clod";
private static final Logger LOGGER = LogUtils.getLogger();
private ClodHttpServer httpServer;
public Clod(IEventBus modEventBus, ModContainer modContainer) {
ModEntities.ENTITY_TYPES.register(modEventBus);
modEventBus.addListener(this::commonSetup);
modEventBus.addListener(this::onAttributeCreation);
NeoForge.EVENT_BUS.register(this);
LOGGER.info("Clod mod initialized!");
}
private void commonSetup(final FMLCommonSetupEvent event) {
LOGGER.info("Clod common setup");
}
private void onAttributeCreation(final EntityAttributeCreationEvent event) {
event.put(ModEntities.CLOD_ENTITY.get(), ClodEntity.createAttributes().build());
}
@SubscribeEvent
public void onRegisterCommands(RegisterCommandsEvent event) {
ClodCommands.register(event);
}
@SubscribeEvent
public void onServerStarted(ServerStartedEvent event) {
httpServer = new ClodHttpServer(event.getServer());
httpServer.start();
}
@SubscribeEvent
public void onServerStopping(ServerStoppingEvent event) {
if (httpServer != null) {
httpServer.stop();
httpServer = null;
}
}
@SubscribeEvent
public void onServerChat(ServerChatEvent event) {
String message = event.getRawText();
if (message.startsWith("!clod ")) {
String stripped = message.substring(6);
String player = event.getPlayer().getName().getString();
if (httpServer != null) {
httpServer.onChatMessage(player, stripped);
}
}
}
@EventBusSubscriber(modid = MODID, value = Dist.CLIENT)
public static class ClientEvents {
@SubscribeEvent
public static void onRegisterRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(ModEntities.CLOD_ENTITY.get(), ClodEntityRenderer::new);
}
@SubscribeEvent
public static void onRegisterLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) {
event.registerLayerDefinition(ClodEntityRenderer.CLOD_LAYER, ClodModel::createBodyLayer);
}
}
}
@@ -0,0 +1,108 @@
package com.clod.clod.client;
import com.clod.clod.entity.ClodEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import org.joml.Vector3f;
@OnlyIn(Dist.CLIENT)
public class ClodEntityRenderer extends MobRenderer<ClodEntity, ClodModel> {
public static final ModelLayerLocation CLOD_LAYER =
new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath("clod", "clod"), "main");
private static final ResourceLocation TEXTURE =
ResourceLocation.fromNamespaceAndPath("clod", "textures/entity/clod.png");
public ClodEntityRenderer(EntityRendererProvider.Context context) {
super(context, new ClodModel(context.bakeLayer(CLOD_LAYER)), 0.4F);
this.addLayer(new ClodEyeGlowLayer(this));
}
@Override
public ResourceLocation getTextureLocation(ClodEntity entity) {
return TEXTURE;
}
@Override
public void render(ClodEntity entity, float entityYaw, float partialTick,
PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
if (!entity.level().isClientSide()) {
return;
}
String state = entity.getClodState();
double x = entity.getX();
double y = entity.getY();
double z = entity.getZ();
switch (state) {
case "idle":
// 10% chance per frame — warm dust drifting upward
if (entity.getRandom().nextFloat() < 0.1F) {
entity.level().addParticle(
new DustParticleOptions(new Vector3f(212F / 255F, 132F / 255F, 91F / 255F), 0.6F),
x + (entity.getRandom().nextDouble() - 0.5) * 0.6,
y + 0.5 + entity.getRandom().nextDouble() * 0.5,
z + (entity.getRandom().nextDouble() - 0.5) * 0.6,
0.0, 0.02, 0.0);
}
break;
case "thinking":
// 3 particles in a spinning ring above head
float tickProgress = entity.tickCount + partialTick;
for (int i = 0; i < 3; i++) {
float angle = tickProgress * 0.15F + (i * (float) (Math.PI * 2.0 / 3.0));
double ringX = x + Mth.cos(angle) * 0.5;
double ringY = y + 2.0;
double ringZ = z + Mth.sin(angle) * 0.5;
entity.level().addParticle(
new DustParticleOptions(new Vector3f(212F / 255F, 132F / 255F, 91F / 255F), 0.8F),
ringX, ringY, ringZ,
0.0, 0.01, 0.0);
}
break;
case "building":
// 30% chance — electric spark near right hand
if (entity.getRandom().nextFloat() < 0.3F) {
float yawRad = entity.yBodyRot * ((float) Math.PI / 180F);
double handX = x - Mth.sin(yawRad) * 0.5 - Mth.cos(yawRad) * 0.4;
double handY = y + 1.0;
double handZ = z + Mth.cos(yawRad) * 0.5 - Mth.sin(yawRad) * 0.4;
entity.level().addParticle(ParticleTypes.ELECTRIC_SPARK,
handX, handY, handZ,
(entity.getRandom().nextDouble() - 0.5) * 0.1,
entity.getRandom().nextDouble() * 0.05,
(entity.getRandom().nextDouble() - 0.5) * 0.1);
}
break;
case "moving":
// 40% chance — warm dust trail behind entity
if (entity.getRandom().nextFloat() < 0.4F) {
Vec3 movement = entity.getDeltaMovement();
entity.level().addParticle(
new DustParticleOptions(new Vector3f(212F / 255F, 132F / 255F, 91F / 255F), 0.5F),
x + (entity.getRandom().nextDouble() - 0.5) * 0.3,
y + 0.1,
z + (entity.getRandom().nextDouble() - 0.5) * 0.3,
-movement.x * 0.5, 0.02, -movement.z * 0.5);
}
break;
}
}
}
@@ -0,0 +1,25 @@
package com.clod.clod.client;
import com.clod.clod.entity.ClodEntity;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.EyesLayer;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public class ClodEyeGlowLayer extends EyesLayer<ClodEntity, ClodModel> {
private static final RenderType CLOD_EYES = RenderType.eyes(
ResourceLocation.fromNamespaceAndPath("clod", "textures/entity/clod_eyes.png"));
public ClodEyeGlowLayer(RenderLayerParent<ClodEntity, ClodModel> renderer) {
super(renderer);
}
@Override
public RenderType renderType() {
return CLOD_EYES;
}
}
@@ -0,0 +1,131 @@
package com.clod.clod.client;
import com.clod.clod.entity.ClodEntity;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeDeformation;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.model.geom.builders.PartDefinition;
import net.minecraft.util.Mth;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public class ClodModel extends HumanoidModel<ClodEntity> {
private final ModelPart antenna;
public ClodModel(ModelPart root) {
super(root);
this.antenna = this.head.getChild("antenna");
}
public static LayerDefinition createBodyLayer() {
MeshDefinition meshdefinition = new MeshDefinition();
PartDefinition partdefinition = meshdefinition.getRoot();
// Head: 10x10x10, pivot at Y=8
PartDefinition head = partdefinition.addOrReplaceChild("head",
CubeListBuilder.create().texOffs(0, 0)
.addBox(-5.0F, -10.0F, -5.0F, 10.0F, 10.0F, 10.0F, CubeDeformation.NONE),
PartPose.offset(0.0F, 8.0F, 0.0F));
// Antenna: child of head, 1x3x1
head.addOrReplaceChild("antenna",
CubeListBuilder.create().texOffs(0, 46)
.addBox(-0.5F, -13.0F, -0.5F, 1.0F, 3.0F, 1.0F, CubeDeformation.NONE),
PartPose.ZERO);
// Hat: empty (required by HumanoidModel parent)
partdefinition.addOrReplaceChild("hat",
CubeListBuilder.create().texOffs(32, 0)
.addBox(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, CubeDeformation.NONE),
PartPose.offset(0.0F, 8.0F, 0.0F));
// Body: 10x8x6, pivot at Y=8
partdefinition.addOrReplaceChild("body",
CubeListBuilder.create().texOffs(0, 20)
.addBox(-5.0F, 0.0F, -3.0F, 10.0F, 8.0F, 6.0F, CubeDeformation.NONE),
PartPose.offset(0.0F, 8.0F, 0.0F));
// Right arm: 4x8x4, pivot at Y=9, X=-7
partdefinition.addOrReplaceChild("right_arm",
CubeListBuilder.create().texOffs(40, 20)
.addBox(-3.0F, -1.0F, -2.0F, 4.0F, 8.0F, 4.0F, CubeDeformation.NONE),
PartPose.offset(-7.0F, 9.0F, 0.0F));
// Left arm: 4x8x4, pivot at Y=9, X=+7
partdefinition.addOrReplaceChild("left_arm",
CubeListBuilder.create().texOffs(40, 32)
.addBox(-1.0F, -1.0F, -2.0F, 4.0F, 8.0F, 4.0F, CubeDeformation.NONE),
PartPose.offset(7.0F, 9.0F, 0.0F));
// Right leg: 4x8x4, pivot at Y=16, X=-2.5
partdefinition.addOrReplaceChild("right_leg",
CubeListBuilder.create().texOffs(0, 34)
.addBox(-2.0F, 0.0F, -2.0F, 4.0F, 8.0F, 4.0F, CubeDeformation.NONE),
PartPose.offset(-2.5F, 16.0F, 0.0F));
// Left leg: 4x8x4, pivot at Y=16, X=+2.5
partdefinition.addOrReplaceChild("left_leg",
CubeListBuilder.create().texOffs(16, 34)
.addBox(-2.0F, 0.0F, -2.0F, 4.0F, 8.0F, 4.0F, CubeDeformation.NONE),
PartPose.offset(2.5F, 16.0F, 0.0F));
return LayerDefinition.create(meshdefinition, 64, 64);
}
@Override
public void setupAnim(ClodEntity entity, float limbSwing, float limbSwingAmount,
float ageInTicks, float netHeadYaw, float headPitch) {
super.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
// Hide the hat part — we don't use it
this.hat.visible = false;
String state = entity.getClodState();
// Default antenna bob
this.antenna.xRot = Mth.sin(ageInTicks * 0.15F) * 0.15F;
switch (state) {
case "thinking":
// Arms forward and together
this.rightArm.xRot = -0.8F;
this.leftArm.xRot = -0.8F;
this.rightArm.zRot = 0.3F; // inward
this.leftArm.zRot = -0.3F; // inward
// Body sway
this.body.zRot = Mth.sin(ageInTicks * 0.08F) * 0.05F;
// Faster antenna bob
this.antenna.xRot = Mth.sin(ageInTicks * 0.4F) * 0.25F;
break;
case "building":
// Right arm extended forward
this.rightArm.xRot = -1.0F;
this.rightArm.zRot = 0.0F;
// Left arm relaxed
this.leftArm.xRot = 0.1F;
this.leftArm.zRot = -0.1F;
break;
case "moving":
// Parent walk cycle handles this — no override needed
break;
case "idle":
default:
// Gentle breathing/sway on arms
float breathe = Mth.sin(ageInTicks * 0.067F) * 0.05F;
this.rightArm.xRot += breathe;
this.leftArm.xRot += breathe;
this.rightArm.zRot = 0.08F + Mth.sin(ageInTicks * 0.067F) * 0.02F;
this.leftArm.zRot = -0.08F - Mth.sin(ageInTicks * 0.067F) * 0.02F;
break;
}
}
}
@@ -0,0 +1,35 @@
package com.clod.clod.command;
import com.clod.clod.entity.ClodEntity;
import com.clod.clod.init.ModEntities;
import com.mojang.brigadier.Command;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
public class ClodCommands {
public static void register(RegisterCommandsEvent event) {
event.getDispatcher().register(
Commands.literal("clod")
.then(Commands.literal("spawn")
.requires(cs -> cs.hasPermission(2))
.executes(ctx -> {
ServerPlayer player = ctx.getSource().getPlayerOrException();
ServerLevel level = player.serverLevel();
ClodEntity entity = new ClodEntity(ModEntities.CLOD_ENTITY.get(), level);
entity.moveTo(player.getX(), player.getY(), player.getZ(),
player.getYRot(), player.getXRot());
level.addFreshEntity(entity);
ClodEntity.instance = entity;
ctx.getSource().sendSuccess(
() -> Component.literal("Clod entity spawned!"), true);
return Command.SINGLE_SUCCESS;
}))
);
}
}
@@ -0,0 +1,80 @@
package com.clod.clod.entity;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.level.Level;
import java.util.Set;
public class ClodEntity extends PathfinderMob {
public static ClodEntity instance;
private static final Set<String> VALID_STATES = Set.of("idle", "thinking", "building", "moving");
private static final EntityDataAccessor<String> STATE =
SynchedEntityData.defineId(ClodEntity.class, EntityDataSerializers.STRING);
private final SimpleContainer inventory = new SimpleContainer(36);
public ClodEntity(EntityType<? extends PathfinderMob> type, Level level) {
super(type, level);
this.getNavigation().setCanFloat(true);
}
public SimpleContainer getInventory() {
return this.inventory;
}
@Override
public void remove(Entity.RemovalReason reason) {
super.remove(reason);
if (instance == this) {
instance = null;
}
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(STATE, "idle");
}
public String getClodState() {
return this.entityData.get(STATE);
}
public void setClodState(String state) {
if (VALID_STATES.contains(state)) {
this.entityData.set(STATE, state);
}
}
@Override
public boolean isInvulnerable() {
return true;
}
@Override
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return false;
}
@Override
protected void registerGoals() {
// No AI goals
}
public static AttributeSupplier.Builder createAttributes() {
return PathfinderMob.createMobAttributes()
.add(Attributes.MAX_HEALTH, 20.0)
.add(Attributes.MOVEMENT_SPEED, 0.3);
}
}
@@ -0,0 +1,20 @@
package com.clod.clod.init;
import com.clod.clod.Clod;
import com.clod.clod.entity.ClodEntity;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobCategory;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
public class ModEntities {
public static final DeferredRegister<EntityType<?>> ENTITY_TYPES =
DeferredRegister.create(Registries.ENTITY_TYPE, Clod.MODID);
public static final DeferredHolder<EntityType<?>, EntityType<ClodEntity>> CLOD_ENTITY =
ENTITY_TYPES.register("clod_entity", () -> EntityType.Builder.of(ClodEntity::new, MobCategory.MISC)
.sized(0.8f, 1.5f)
.build(Clod.MODID + ":clod_entity"));
}
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

+6
View File
@@ -0,0 +1,6 @@
{
"pack": {
"description": "Clod resources",
"pack_format": 34
}
}
@@ -0,0 +1,24 @@
modLoader="javafml"
loaderVersion="${loader_version_range}"
license="${mod_license}"
[[mods]]
modId="${mod_id}"
version="${mod_version}"
displayName="${mod_name}"
authors="${mod_authors}"
description='''${mod_description}'''
[[dependencies.${mod_id}]]
modId="neoforge"
type="required"
versionRange="${neo_version_range}"
ordering="NONE"
side="BOTH"
[[dependencies.${mod_id}]]
modId="minecraft"
type="required"
versionRange="${minecraft_version_range}"
ordering="NONE"
side="BOTH"