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; package net.Chipperfluff.chipi;
import net.Chipperfluff.chipi.advancement.ModCriteria; import net.Chipperfluff.chipi.advancement.ModCriteria;
import net.Chipperfluff.chipi.block.ChipperPortalBlock;
import net.Chipperfluff.chipi.block.ModBlocks; 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.ChipiBlessingEvents;
import net.Chipperfluff.chipi.effect.ChipiHungerHandler; import net.Chipperfluff.chipi.effect.ChipiHungerHandler;
import net.Chipperfluff.chipi.effect.ModEffects; import net.Chipperfluff.chipi.effect.ModEffects;
import net.Chipperfluff.chipi.entity.ModEntities; import net.Chipperfluff.chipi.entity.ModEntities;
import net.Chipperfluff.chipi.entity.SpawnLogic;
import net.Chipperfluff.chipi.entity.custom.MepEntity; import net.Chipperfluff.chipi.entity.custom.MepEntity;
import net.Chipperfluff.chipi.item.ModItemGroups; import net.Chipperfluff.chipi.item.ModItemGroups;
import net.Chipperfluff.chipi.item.ModItems; 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.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.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 class ChipiMod implements ModInitializer {
public static final String MOD_ID = "chipi"; 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 @Override
public void onInitialize() { public void onInitialize() {
ModBlocks.register(); ModBlocks.register();
ModItems.register(); ModItems.register();
ModItemGroups.register(); ModItemGroups.register();
ModCriteria.register();
ModEntities.register();
ModEffects.register(); ModEffects.register();
ModCriteria.register();
ChipiBlessingEvents.register(); ChipiBlessingEvents.register();
ChipiHungerHandler.register(); ChipiHungerHandler.register();
SpawnLogic.register();
CommandHandler.register(); CommandHandler.register();
FabricDefaultAttributeRegistry.register(ModEntities.MEP, MepEntity.createMepAttributes()); FabricDefaultAttributeRegistry.register(ModEntities.MEP, MepEntity.createMepAttributes());
BiomeModifications.addFeature( ChipiServerEvents.register();
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);
} }
} }

View File

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

View File

@ -12,5 +12,6 @@ public class ChipiClient implements ClientModInitializer {
public void onInitializeClient() { public void onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.CHIPPER_PORTAL, RenderLayer.getTranslucent()); BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.CHIPPER_PORTAL, RenderLayer.getTranslucent());
ModEntityRenderers.register(); 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; package net.Chipperfluff.chipi.entity;
import net.Chipperfluff.chipi.ChipiMod;
import net.Chipperfluff.chipi.entity.custom.MepEntity; import net.Chipperfluff.chipi.entity.custom.MepEntity;
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder;
import net.minecraft.entity.EntityDimensions; import net.minecraft.entity.EntityDimensions;
import net.minecraft.entity.SpawnGroup;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry; import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class ModEntities { public final class ModEntities {
public static final EntityType<MepEntity> MEP = Registry.register( public static final EntityType<MepEntity> MEP =
Registries.ENTITY_TYPE, Registry.register(
new Identifier(ChipiMod.MOD_ID, "mep"), Registries.ENTITY_TYPE,
FabricEntityTypeBuilder.createMob() new Identifier("chipi", "mep"),
.entityFactory(MepEntity::new) FabricEntityTypeBuilder
.dimensions(EntityDimensions.fixed(0.6f, 1.95f)) .create(SpawnGroup.CREATURE, MepEntity::new)
.build() .dimensions(EntityDimensions.fixed(0.6f, 1.95f))
); .build()
);
public static void register() { private ModEntities() {
// called from mod init
} }
} }

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 { public class MepEntity extends PathAwareEntity {
private static final int FORGET_TARGET_AFTER_TICKS = 100;
private boolean angryAtPlayer = false; private boolean angryAtPlayer = false;
private int ticksSinceLastSeen = 0;
public MepEntity(EntityType<? extends PathAwareEntity> entityType, World world) { public MepEntity(EntityType<? extends PathAwareEntity> entityType, World world) {
super(entityType, world); super(entityType, world);
@ -42,7 +45,7 @@ public class MepEntity extends PathAwareEntity {
this, this,
PlayerEntity.class, PlayerEntity.class,
true, 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() return PathAwareEntity.createMobAttributes()
.add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0) .add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0)
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 4.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 @Override
public boolean damage(DamageSource source, float amount) { public boolean damage(DamageSource source, float amount) {
if (source.getAttacker() instanceof PlayerEntity) { if (source.getAttacker() instanceof PlayerEntity) {
angryAtPlayer = true; angryAtPlayer = true;
ticksSinceLastSeen = 0;
} }
return super.damage(source, amount); return super.damage(source, amount);
} }
@ -68,18 +73,39 @@ public class MepEntity extends PathAwareEntity {
LivingEntity target = this.getTarget(); LivingEntity target = this.getTarget();
if (angryAtPlayer) { if (!(target instanceof PlayerEntity player)) {
if (!(target instanceof PlayerEntity) || !this.canSee(target)) { ticksSinceLastSeen = 0;
angryAtPlayer = false; angryAtPlayer = false;
this.setTarget(null);
}
return; return;
} }
if (target instanceof PlayerEntity player && isPlayerProtected(player)) { // Protected players instantly cancel targeting
this.setTarget(null); if (isPlayerProtected(player)) {
this.getNavigation().stop(); 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) { private static boolean isPlayerProtected(PlayerEntity player) {
@ -90,6 +116,19 @@ public class MepEntity extends PathAwareEntity {
return false; 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", "effect.chipi.chipi_blessing": "Chipi's Blessing",
"entity.chipi.mep": "Merp :3",
"block.chipi.void_block": "Void Block", "block.chipi.void_block": "Void Block",
"block.chipi.chipper_frame": "Chipper Frame", "block.chipi.chipper_frame": "Chipper Frame",
"block.chipi.chipper_portal": "Chipper Portal", "block.chipi.chipper_portal": "Chipper Portal",

View File

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