mew let mep spawn

This commit is contained in:
Chipperfluff 2025-12-19 16:06:12 +01:00
parent 9b44fcf4e0
commit 8cfc27cb70
10 changed files with 343 additions and 135 deletions

View File

@ -1,135 +1,42 @@
package net.Chipperfluff.chipi;
import net.Chipperfluff.chipi.advancement.ModCriteria;
import net.Chipperfluff.chipi.block.ChipperPortalBlock;
import net.Chipperfluff.chipi.block.ModBlocks;
import net.Chipperfluff.chipi.command.ChpCommand;
import net.Chipperfluff.chipi.command.CommandHandler;
import net.Chipperfluff.chipi.effect.ChipiBlessingEvents;
import net.Chipperfluff.chipi.effect.ChipiHungerHandler;
import net.Chipperfluff.chipi.effect.ModEffects;
import net.Chipperfluff.chipi.entity.ModEntities;
import net.Chipperfluff.chipi.entity.SpawnLogic;
import net.Chipperfluff.chipi.entity.custom.MepEntity;
import net.Chipperfluff.chipi.item.ModItemGroups;
import net.Chipperfluff.chipi.item.ModItems;
import net.Chipperfluff.chipi.world.gen.ChipiDungeonGenerator;
import net.Chipperfluff.chipi.server.ChipiServerEvents;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
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.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.structure.StructurePlacementData;
import net.minecraft.structure.StructureTemplate;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameRules;
import net.minecraft.world.World;
import net.minecraft.world.gen.GenerationStep;
import net.Chipperfluff.chipi.command.CommandHandler;
public class ChipiMod implements ModInitializer {
public static final String MOD_ID = "chipi";
public static final RegistryKey<World> CHIPI_DIMENSION_KEY = RegistryKey.of(RegistryKeys.WORLD, new Identifier("chipi", "chipi_dimension"));
private static final Identifier SPAWN_STRUCTURE = new Identifier("chipi", "spawn");
private static MinecraftServer SERVER;
@Override
public void onInitialize() {
ModBlocks.register();
ModItems.register();
ModItemGroups.register();
ModCriteria.register();
ModEntities.register();
ModEffects.register();
ModCriteria.register();
ChipiBlessingEvents.register();
ChipiHungerHandler.register();
SpawnLogic.register();
CommandHandler.register();
FabricDefaultAttributeRegistry.register(ModEntities.MEP, MepEntity.createMepAttributes());
BiomeModifications.addFeature(
BiomeSelectors.foundInOverworld(),
GenerationStep.Feature.UNDERGROUND_ORES,
RegistryKey.of(RegistryKeys.PLACED_FEATURE, new Identifier("chipi", "chipper_ore"))
);
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
SERVER = server;
});
ServerTickEvents.END_WORLD_TICK.register(world -> {
if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) {
return;
}
for (PlayerEntity player : world.getPlayers()) {
if (player.getBlockY() >= 50) {
continue;
}
ChipperPortalBlock.teleportToChipiSpawn(world, player);
}
});
ServerWorldEvents.LOAD.register((server, world) -> {
if (!world.getRegistryKey().equals(CHIPI_DIMENSION_KEY)) {
return;
}
world.setTimeOfDay(18000);
world.getGameRules()
.get(GameRules.DO_DAYLIGHT_CYCLE)
.set(false, server);
SpawnPlacedState state = world.getPersistentStateManager().getOrCreate(
SpawnPlacedState::fromNbt,
SpawnPlacedState::new,
"chipi_spawn"
);
if (state.placed) {
return;
}
StructureTemplate spawnTemplate = world.getStructureTemplateManager().getTemplate(SPAWN_STRUCTURE).orElse(null);
if (spawnTemplate == null) {
System.err.println("[CHIPI] spawn.nbt not found!");
return;
}
BlockPos spawnCenter = 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);
state.placed = true;
state.markDirty();
System.out.println("[CHIPI] Spawn + initial dungeon generated");
});
}
public static ServerWorld getChipiWorld() {
if (SERVER == null) {
return null;
}
return SERVER.getWorld(CHIPI_DIMENSION_KEY);
ChipiServerEvents.register();
}
}

View File

@ -52,16 +52,26 @@ public class ChipperPortalBlock extends Block {
public static void teleportToChipiSpawn(ServerWorld targetWorld, PlayerEntity player) {
BlockPos spawn = resolveSafeSpawn(targetWorld);
player.setVelocity(0.0, 0.0, 0.0);
player.fallDistance = 0.0f;
player.setOnGround(true);
player.teleport(
targetWorld,
spawn.getX() + 0.5,
spawn.getY(),
spawn.getZ() + 0.5,
EnumSet.noneOf(PositionFlag.class),
player.getYaw(),
player.getPitch());
targetWorld,
spawn.getX() + 0.5,
spawn.getY(),
spawn.getZ() + 0.5,
EnumSet.noneOf(PositionFlag.class),
player.getYaw(),
player.getPitch()
);
player.setVelocity(0.0, 0.0, 0.0);
player.fallDistance = 0.0f;
player.setOnGround(true);
}
public static BlockPos resolveSafeSpawn(ServerWorld targetWorld) {
BlockPos spawn = DEFAULT_SPAWN;
BlockPos under = spawn.down();

View File

@ -12,5 +12,6 @@ public class ChipiClient implements ClientModInitializer {
public void onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.CHIPPER_PORTAL, RenderLayer.getTranslucent());
ModEntityRenderers.register();
ModTooltips.register();
}
}

View File

@ -0,0 +1,63 @@
package net.Chipperfluff.chipi.client;
import net.Chipperfluff.chipi.block.ModBlocks;
import net.Chipperfluff.chipi.item.ModItems;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
import net.minecraft.text.Text;
public final class ModTooltips {
private ModTooltips() {
}
public static void register() {
ItemTooltipCallback.EVENT.register((stack, context, lines) -> {
// ===== BLOCK ITEMS =====
if (stack.isOf(ModBlocks.VOID_BLOCK.asItem()))
lines.add(Text.translatable("tooltip.chipi.void_block"));
if (stack.isOf(ModBlocks.CHIPPER_FRAME.asItem()))
lines.add(Text.translatable("tooltip.chipi.chipper_frame"));
if (stack.isOf(ModBlocks.CHIPPER_PORTAL.asItem()))
lines.add(Text.translatable("tooltip.chipi.chipper_portal"));
if (stack.isOf(ModBlocks.CHIPPER_ORE.asItem()))
lines.add(Text.translatable("tooltip.chipi.chipper_ore"));
if (stack.isOf(ModBlocks.CHIPPER_ALLOY_BLOCK.asItem()))
lines.add(Text.translatable("tooltip.chipi.chipper_alloy_block"));
// ===== ITEMS =====
if (stack.isOf(ModItems.NUT))
lines.add(Text.translatable("tooltip.chipi.nut"));
if (stack.isOf(ModItems.RAW_CHIPPER_ORE))
lines.add(Text.translatable("tooltip.chipi.raw_chipper_ore"));
if (stack.isOf(ModItems.CHIPPER_INGOT))
lines.add(Text.translatable("tooltip.chipi.chipper_ingot"));
if (stack.isOf(ModItems.CHIPPER_ALLOY))
lines.add(Text.translatable("tooltip.chipi.chipper_alloy"));
if (stack.isOf(ModItems.MEP_SPAWN_EGG))
lines.add(Text.translatable("tooltip.chipi.mep_spawn_egg"));
// ===== ARMOR =====
if (stack.isOf(ModItems.CHIPPER_HELMET))
lines.add(Text.translatable("tooltip.chipi.chipper_helmet"));
if (stack.isOf(ModItems.CHIPPER_CHESTPLATE))
lines.add(Text.translatable("tooltip.chipi.chipper_chestplate"));
if (stack.isOf(ModItems.CHIPPER_LEGGINGS))
lines.add(Text.translatable("tooltip.chipi.chipper_leggings"));
if (stack.isOf(ModItems.CHIPPER_BOOTS))
lines.add(Text.translatable("tooltip.chipi.chipper_boots"));
});
}
}

View File

@ -1,26 +1,28 @@
package net.Chipperfluff.chipi.entity;
import net.Chipperfluff.chipi.ChipiMod;
import net.Chipperfluff.chipi.entity.custom.MepEntity;
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder;
import net.minecraft.entity.EntityDimensions;
import net.minecraft.entity.SpawnGroup;
import net.minecraft.entity.EntityType;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;
public class ModEntities {
public final class ModEntities {
public static final EntityType<MepEntity> MEP = Registry.register(
Registries.ENTITY_TYPE,
new Identifier(ChipiMod.MOD_ID, "mep"),
FabricEntityTypeBuilder.createMob()
.entityFactory(MepEntity::new)
.dimensions(EntityDimensions.fixed(0.6f, 1.95f))
.build()
);
public static final EntityType<MepEntity> MEP =
Registry.register(
Registries.ENTITY_TYPE,
new Identifier("chipi", "mep"),
FabricEntityTypeBuilder
.create(SpawnGroup.CREATURE, MepEntity::new)
.dimensions(EntityDimensions.fixed(0.6f, 1.95f))
.build()
);
public static void register() {
// called from mod init
private ModEntities() {
}
}

View File

@ -0,0 +1,61 @@
package net.Chipperfluff.chipi.entity;
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnGroup;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.SpawnRestriction;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.Heightmap;
import net.minecraft.world.ServerWorldAccess;
import net.minecraft.world.biome.Biome;
public final class SpawnLogic {
private static final RegistryKey<Biome> VOID_BIOME =
RegistryKey.of(RegistryKeys.BIOME, new Identifier("chipi", "void"));
private SpawnLogic() {
}
public static void register() {
BiomeModifications.addSpawn(
BiomeSelectors.includeByKey(VOID_BIOME),
SpawnGroup.MONSTER,
ModEntities.MEP,
10,
1,
4
);
SpawnRestriction.register(
ModEntities.MEP,
SpawnRestriction.Location.ON_GROUND,
Heightmap.Type.MOTION_BLOCKING_NO_LEAVES,
SpawnLogic::canSpawn
);
}
private static boolean canSpawn(EntityType<? extends MobEntity> type, ServerWorldAccess world, SpawnReason reason, BlockPos pos, Random random) {
int y = pos.getY();
if (y < 87 || y > 90) return false;
if (pos.getZ() < 18) return false;
BlockState below = world.getBlockState(pos.down());
boolean can_spawn = below.isOf(Blocks.POLISHED_BLACKSTONE_BRICKS);
System.out.println("[MEP ENTITY] spawn check pos=" + pos + " below=" + below.getBlock());
return can_spawn;
}
}

View File

@ -22,7 +22,10 @@ import net.minecraft.world.World;
public class MepEntity extends PathAwareEntity {
private static final int FORGET_TARGET_AFTER_TICKS = 100;
private boolean angryAtPlayer = false;
private int ticksSinceLastSeen = 0;
public MepEntity(EntityType<? extends PathAwareEntity> entityType, World world) {
super(entityType, world);
@ -42,7 +45,7 @@ public class MepEntity extends PathAwareEntity {
this,
PlayerEntity.class,
true,
target -> target instanceof PlayerEntity player && (angryAtPlayer || !isPlayerProtected(player))
target -> target instanceof PlayerEntity player && !isPlayerProtected(player)
)
);
}
@ -51,13 +54,15 @@ public class MepEntity extends PathAwareEntity {
return PathAwareEntity.createMobAttributes()
.add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0)
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 4.0)
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25);
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25)
.add(EntityAttributes.GENERIC_FOLLOW_RANGE, 32.0);
}
@Override
public boolean damage(DamageSource source, float amount) {
if (source.getAttacker() instanceof PlayerEntity) {
angryAtPlayer = true;
ticksSinceLastSeen = 0;
}
return super.damage(source, amount);
}
@ -68,18 +73,39 @@ public class MepEntity extends PathAwareEntity {
LivingEntity target = this.getTarget();
if (angryAtPlayer) {
if (!(target instanceof PlayerEntity) || !this.canSee(target)) {
angryAtPlayer = false;
this.setTarget(null);
}
if (!(target instanceof PlayerEntity player)) {
ticksSinceLastSeen = 0;
angryAtPlayer = false;
return;
}
if (target instanceof PlayerEntity player && isPlayerProtected(player)) {
this.setTarget(null);
this.getNavigation().stop();
// Protected players instantly cancel targeting
if (isPlayerProtected(player)) {
clearTarget();
return;
}
if (this.canSee(player)) {
ticksSinceLastSeen = 0;
angryAtPlayer = true;
this.getNavigation().startMovingTo(player, 1.2D);
} else {
ticksSinceLastSeen++;
// Keep chasing for a while even without LOS
if (ticksSinceLastSeen <= FORGET_TARGET_AFTER_TICKS) {
this.getNavigation().startMovingTo(player, 1.2D);
} else {
clearTarget();
}
}
}
private void clearTarget() {
angryAtPlayer = false;
ticksSinceLastSeen = 0;
this.setTarget(null);
this.getNavigation().stop();
}
private static boolean isPlayerProtected(PlayerEntity player) {
@ -90,6 +116,19 @@ public class MepEntity extends PathAwareEntity {
return false;
}
return state.getBlock() instanceof StairsBlock || state.getBlock() instanceof SlabBlock;
return state.getBlock() instanceof StairsBlock
|| state.getBlock() instanceof SlabBlock;
}
// === Despawn prevention ===
@Override
public boolean cannotDespawn() {
return true;
}
@Override
public boolean canImmediatelyDespawn(double distanceSquared) {
return false;
}
}

View File

@ -0,0 +1,115 @@
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.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.entity.player.PlayerEntity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.structure.StructurePlacementData;
import net.minecraft.structure.StructureTemplate;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameRules;
import net.minecraft.world.World;
public final class ChipiServerEvents {
public static final RegistryKey<World> CHIPI_DIMENSION_KEY =
RegistryKey.of(RegistryKeys.WORLD, new Identifier("chipi", "chipi_dimension"));
private static final Identifier SPAWN_STRUCTURE =
new Identifier("chipi", "spawn");
private static MinecraftServer SERVER;
private ChipiServerEvents() {}
public static void register() {
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
SERVER = server;
});
ServerTickEvents.END_WORLD_TICK.register(ChipiServerEvents::handleVoidFailsafe);
ServerWorldEvents.LOAD.register(ChipiServerEvents::onWorldLoad);
}
// ============================================================
// Tick logic
// ============================================================
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);
}
}
// ============================================================
// 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);
SpawnPlacedState state = world.getPersistentStateManager().getOrCreate(
SpawnPlacedState::fromNbt,
SpawnPlacedState::new,
"chipi_spawn"
);
if (state.placed) return;
StructureTemplate spawnTemplate =
world.getStructureTemplateManager()
.getTemplate(SPAWN_STRUCTURE)
.orElse(null);
if (spawnTemplate == null) {
System.err.println("[CHIPI] spawn.nbt not found!");
return;
}
BlockPos spawnCenter = 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);
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);
}
}

View File

@ -6,6 +6,8 @@
"effect.chipi.chipi_blessing": "Chipi's Blessing",
"entity.chipi.mep": "Merp :3",
"block.chipi.void_block": "Void Block",
"block.chipi.chipper_frame": "Chipper Frame",
"block.chipi.chipper_portal": "Chipper Portal",

View File

@ -3,7 +3,7 @@
"downfall": 0,
"has_precipitation": false,
"temperature_modifier": "frozen",
"creature_spawn_probability": 0,
"creature_spawn_probability": 0.1,
"effects": {
"sky_color": 0,
"fog_color": 0,
@ -13,8 +13,16 @@
"foliage_color": 0,
"grass_color_modifier": "none"
},
"spawners": {},
"spawners": {
"monster": [],
"creature": [],
"ambient": [],
"axolotls": [],
"underground_water_creature": [],
"water_creature": [],
"water_ambient": []
},
"spawn_costs": {},
"carvers": {},
"features": []
}
}