From ad448863b76420d6a4ce985ace2dcbc4aeeb355a Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sat, 13 Jun 2026 14:40:45 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E5=A4=A7=E4=BE=BF=E7=A9=BA=E5=B2=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/altnoir/poopsky/PoopSky.java | 2 + .../poopsky/worldgen/PSStructures.java | 31 ++++ .../worldgen/PSVoidChunkGenerator.java | 150 +++++++++++++--- .../worldgen/structure/PoopIslandPiece.java | 166 ++++++++++++++++++ .../structure/PoopIslandStructure.java | 146 +++++++++++++++ .../poopsky/structure/islands/dirt/0x1x0.nbt | Bin 0 -> 5611 bytes .../structure/islands/dirt/11x1x11.nbt | Bin 0 -> 13352 bytes .../poopsky/structure/islands/dirt/2x2x4.nbt | Bin 0 -> 8262 bytes .../poopsky/structure/islands/dirt/4x1x6.nbt | Bin 0 -> 8262 bytes .../poopsky/structure/islands/dirt/8x1x11.nbt | Bin 0 -> 12243 bytes .../worldgen/structure/poop_island.json | 7 + .../worldgen/structure_set/poop_islands.json | 14 ++ .../worldgen/world_preset/poopsky.json | 3 +- 13 files changed, 496 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/altnoir/poopsky/worldgen/PSStructures.java create mode 100644 src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java create mode 100644 src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java create mode 100644 src/main/resources/data/poopsky/structure/islands/dirt/0x1x0.nbt create mode 100644 src/main/resources/data/poopsky/structure/islands/dirt/11x1x11.nbt create mode 100644 src/main/resources/data/poopsky/structure/islands/dirt/2x2x4.nbt create mode 100644 src/main/resources/data/poopsky/structure/islands/dirt/4x1x6.nbt create mode 100644 src/main/resources/data/poopsky/structure/islands/dirt/8x1x11.nbt create mode 100644 src/main/resources/data/poopsky/worldgen/structure/poop_island.json create mode 100644 src/main/resources/data/poopsky/worldgen/structure_set/poop_islands.json diff --git a/src/main/java/com/altnoir/poopsky/PoopSky.java b/src/main/java/com/altnoir/poopsky/PoopSky.java index 787df63b..1574a008 100644 --- a/src/main/java/com/altnoir/poopsky/PoopSky.java +++ b/src/main/java/com/altnoir/poopsky/PoopSky.java @@ -9,6 +9,7 @@ import com.altnoir.poopsky.network.PSNetworking; import com.altnoir.poopsky.villager.PSVillagers; import com.altnoir.poopsky.worldgen.PSChunkGenerators; +import com.altnoir.poopsky.worldgen.PSStructures; import com.altnoir.poopsky.worldgen.foliage.PSFoliagePlacerTypes; import com.mojang.logging.LogUtils; import net.minecraft.core.BlockPos; @@ -52,6 +53,7 @@ public PoopSky(IEventBus modEventBus, ModContainer modContainer) { PSItems.register(modEventBus); PEntityType.register(modEventBus); PSFoliagePlacerTypes.register(modEventBus); + PSStructures.register(modEventBus); PSChunkGenerators.register(modEventBus); PSItemGroups.register(modEventBus); diff --git a/src/main/java/com/altnoir/poopsky/worldgen/PSStructures.java b/src/main/java/com/altnoir/poopsky/worldgen/PSStructures.java new file mode 100644 index 00000000..c4111dc1 --- /dev/null +++ b/src/main/java/com/altnoir/poopsky/worldgen/PSStructures.java @@ -0,0 +1,31 @@ +package com.altnoir.poopsky.worldgen; + +import com.altnoir.poopsky.PoopSky; +import com.altnoir.poopsky.worldgen.structure.PoopIslandPiece; +import com.altnoir.poopsky.worldgen.structure.PoopIslandStructure; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.levelgen.structure.StructureType; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; + +public final class PSStructures { + public static final DeferredRegister> STRUCTURE_TYPES = + DeferredRegister.create(Registries.STRUCTURE_TYPE, PoopSky.MOD_ID); + public static final DeferredRegister STRUCTURE_PIECES = + DeferredRegister.create(Registries.STRUCTURE_PIECE, PoopSky.MOD_ID); + + public static final DeferredHolder, StructureType> POOP_ISLAND = + STRUCTURE_TYPES.register("poop_island", () -> () -> PoopIslandStructure.CODEC); + public static final DeferredHolder POOP_ISLAND_PIECE = + STRUCTURE_PIECES.register("poop_island", () -> (StructurePieceType.StructureTemplateType) PoopIslandPiece::new); + + private PSStructures() { + } + + public static void register(IEventBus eventBus) { + STRUCTURE_TYPES.register(eventBus); + STRUCTURE_PIECES.register(eventBus); + } +} diff --git a/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java b/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java index 7d90dac1..94cfc641 100644 --- a/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java +++ b/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java @@ -1,6 +1,8 @@ package com.altnoir.poopsky.worldgen; import com.altnoir.poopsky.Config; +import com.altnoir.poopsky.PoopSky; +import com.altnoir.poopsky.worldgen.structure.PoopIslandStructure; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; @@ -10,6 +12,7 @@ import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.SectionPos; @@ -23,6 +26,7 @@ import net.minecraft.world.level.NoiseColumn; import net.minecraft.world.level.StructureManager; import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.block.Blocks; @@ -32,6 +36,7 @@ import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; import net.minecraft.world.level.levelgen.GenerationStep; import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.LegacyRandomSource; import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; import net.minecraft.world.level.levelgen.RandomState; @@ -42,16 +47,19 @@ import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.StructureSet.StructureSelectionEntry; import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -126,7 +134,19 @@ private static AllowedStructureSets ofKeys(List> keys) private boolean contains(Holder holder) { return tag.map(holder::is) - .orElseGet(() -> holder.unwrapKey().filter(keys::contains).isPresent()); + .orElseGet(() -> { + Set allowedLocations = keys.stream() + .map(ResourceKey::location) + .collect(Collectors.toUnmodifiableSet()); + + return holder.unwrapKey() + .map(key -> allowedLocations.contains(key.location())) + .orElse(false) + || holder.value().structures().stream() + .map(StructureSelectionEntry::structure) + .map(Holder::unwrapKey) + .anyMatch(key -> key.map(ResourceKey::location).filter(allowedLocations::contains).isPresent()); + }); } } @@ -196,22 +216,15 @@ public void createStructures( ChunkAccess chunk, StructureTemplateManager templateManager ) { - super.createStructures(registries, structureState, structureManager, chunk, templateManager); - - if (allowedStructureSets.isEmpty()) { - return; + if (generateNormal || allowedStructureSets.isEmpty()) { + super.createStructures(registries, structureState, structureManager, chunk, templateManager); + } else { + createAllowedStructures(registries, structureState, structureManager, chunk, templateManager); } - Set allowedStructures = resolveAllowedStructures(registries); - Map filteredStarts = new HashMap<>(); - - chunk.getAllStarts().forEach((structure, start) -> { - if (allowedStructures.contains(structure)) { - filteredStarts.put(structure, start); - } - }); - - chunk.setAllStarts(filteredStarts); + if (!generateNormal && settings.is(ResourceLocation.parse("minecraft:overworld")) && isStructureAllowed(registries, PoopSky.loc("poop_island"))) { + PoopIslandStructure.addGuaranteedSpawnStart(registries, chunk, structureManager, templateManager, structureState.getLevelSeed()); + } } @Override @@ -249,11 +262,107 @@ private Set resolveAllowedStructures(RegistryAccess registries) { return registry.holders() .filter(allowed::contains) .flatMap(holder -> holder.value().structures().stream()) - .map(StructureSet.StructureSelectionEntry::structure) + .map(StructureSelectionEntry::structure) .map(Holder::value) .collect(Collectors.toUnmodifiableSet()); } + private boolean isStructureAllowed(RegistryAccess registries, ResourceLocation structureId) { + if (allowedStructureSets.isEmpty()) { + return true; + } + + Registry structureRegistry = registries.registryOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.get(structureId); + return structure != null && resolveAllowedStructures(registries).contains(structure); + } + + private void createAllowedStructures(RegistryAccess registries, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess chunk, StructureTemplateManager templateManager) { + ChunkPos chunkPos = chunk.getPos(); + SectionPos sectionPos = SectionPos.bottomOf(chunk); + RandomState randomState = structureState.randomState(); + AllowedStructureSets allowed = allowedStructureSets.orElseThrow(); + + for (Holder structureSetHolder : structureState.possibleStructureSets()) { + if (!allowed.contains(structureSetHolder)) { + continue; + } + + StructureSet structureSet = structureSetHolder.value(); + StructurePlacement placement = structureSet.placement(); + List entries = structureSet.structures(); + + boolean hasExistingStart = false; + for (StructureSelectionEntry entry : entries) { + StructureStart start = structureManager.getStartForStructure(sectionPos, entry.structure().value(), chunk); + if (start != null && start.isValid()) { + hasExistingStart = true; + break; + } + } + + if (hasExistingStart || !placement.isStructureChunk(structureState, chunkPos.x, chunkPos.z)) { + continue; + } + + if (entries.size() == 1) { + tryGenerateStructure(entries.getFirst(), structureManager, registries, randomState, templateManager, structureState.getLevelSeed(), chunk, chunkPos, sectionPos); + continue; + } + + ArrayList shuffledEntries = new ArrayList<>(entries); + WorldgenRandom random = new WorldgenRandom(new LegacyRandomSource(0L)); + random.setLargeFeatureSeed(structureState.getLevelSeed(), chunkPos.x, chunkPos.z); + int totalWeight = 0; + + for (StructureSelectionEntry entry : shuffledEntries) { + totalWeight += entry.weight(); + } + + while (!shuffledEntries.isEmpty()) { + int targetWeight = random.nextInt(totalWeight); + int index = 0; + + for (StructureSelectionEntry entry : shuffledEntries) { + targetWeight -= entry.weight(); + if (targetWeight < 0) { + break; + } + + index++; + } + + StructureSelectionEntry entry = shuffledEntries.get(index); + if (tryGenerateStructure(entry, structureManager, registries, randomState, templateManager, structureState.getLevelSeed(), chunk, chunkPos, sectionPos)) { + break; + } + + shuffledEntries.remove(index); + totalWeight -= entry.weight(); + } + } + } + + private boolean tryGenerateStructure(StructureSelectionEntry structureSelectionEntry, StructureManager structureManager, + RegistryAccess registries, RandomState randomState, + StructureTemplateManager templateManager, long seed, + ChunkAccess chunk, ChunkPos chunkPos, SectionPos sectionPos + ) { + Structure structure = structureSelectionEntry.structure().value(); + StructureStart existingStart = structureManager.getStartForStructure(sectionPos, structure, chunk); + int references = existingStart != null ? existingStart.getReferences() : 0; + HolderSet biomes = structure.biomes(); + Predicate> biomePredicate = biomes::contains; + StructureStart start = structure.generate(registries, this, this.biomeSource, randomState, templateManager, seed, chunkPos, references, chunk, biomePredicate); + + if (start.isValid()) { + structureManager.setStartForStructure(sectionPos, structure, start, chunk); + return true; + } + + return false; + } + private void placeStructuresOnly( WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) { ChunkPos chunkPos = chunk.getPos(); @@ -265,14 +374,11 @@ private void placeStructuresOnly( WorldGenLevel level, ChunkAccess chunk, Struct BlockPos origin = sectionPos.origin(); RegistryAccess registries = level.registryAccess(); Registry structureRegistry = registries.registryOrThrow(Registries.STRUCTURE); - - Set allowedStructures = allowedStructureSets.isPresent() - ? resolveAllowedStructures(registries) - : null; + Set allowedStructures = resolveAllowedStructures(registries); Map> structuresByStep = structureRegistry.stream() - .filter(structure -> allowedStructures == null || allowedStructures.contains(structure)) + .filter(structure -> allowedStructureSets.isEmpty() || allowedStructures.contains(structure)) .collect(Collectors.groupingBy(structure -> structure.step().ordinal())); WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); diff --git a/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java new file mode 100644 index 00000000..e4f953ea --- /dev/null +++ b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java @@ -0,0 +1,166 @@ +package com.altnoir.poopsky.worldgen.structure; + +import com.altnoir.poopsky.block.PSBlocks; +import com.altnoir.poopsky.entity.p.PoolimeEntity; +import com.altnoir.poopsky.init.PEntityType; +import com.altnoir.poopsky.worldgen.PSStructures; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.TemplateStructurePiece; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext; +import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; + +import java.util.List; + +public class PoopIslandPiece extends TemplateStructurePiece { + private static final String ROTATION_KEY = "Rotation"; + + public PoopIslandPiece(StructureTemplateManager manager, ResourceLocation templateId, BlockPos pos, Rotation rotation) { + super(PSStructures.POOP_ISLAND_PIECE.get(), 0, manager, templateId, templateId.toString(), placeSettings(rotation), pos); + } + + public PoopIslandPiece(StructureTemplateManager manager, CompoundTag tag) { + super(PSStructures.POOP_ISLAND_PIECE.get(), tag, manager, id -> placeSettings(readRotation(tag))); + } + + public static StructurePlaceSettings placeSettings(Rotation rotation) { + return new StructurePlaceSettings() + .setRotation(rotation) + .setIgnoreEntities(false) + .setFinalizeEntities(true) + .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); + } + + @Override + protected void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag tag) { + super.addAdditionalSaveData(context, tag); + tag.putString(ROTATION_KEY, this.placeSettings.getRotation().name()); + } + + @Override + public void postProcess( + WorldGenLevel level, + StructureManager structureManager, + ChunkGenerator generator, + RandomSource random, + BoundingBox box, + ChunkPos chunkPos, + BlockPos pos + ) { + super.postProcess(level, structureManager, generator, random, box, chunkPos, pos); + RandomSource islandRandom = RandomSource.create(Mth.getSeed(this.templatePosition)); + placeRandomPoopTree(level, islandRandom, this.template, this.templatePosition, this.placeSettings, box); + spawnRandomPoolimes(level, islandRandom, this.template, this.templatePosition, this.placeSettings, box); + } + + @Override + protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) { + } + + public static void placeRandomPoopTree(WorldGenLevel level, RandomSource random, StructureTemplate template, BlockPos origin, StructurePlaceSettings settings, BoundingBox box) { + List treeMarkers = template.filterBlocks(origin, settings, Blocks.STRUCTURE_BLOCK) + .stream() + .filter(PoopIslandPiece::isTreeMarker) + .toList(); + + if (treeMarkers.isEmpty() || random.nextFloat() >= 0.7F) { + return; + } + + BlockPos treePos = treeMarkers.get(random.nextInt(treeMarkers.size())).pos(); + placePoopTree(level, random, treePos); + } + + public static void spawnRandomPoolimes(WorldGenLevel level, RandomSource random, StructureTemplate template, BlockPos origin, StructurePlaceSettings settings, BoundingBox box) { + List poolimeBlocks = template.filterBlocks(origin, settings, PSBlocks.POOLIME_BLOCK.get()) + .stream() + .filter(blockInfo -> level.getBlockState(blockInfo.pos().above()).canBeReplaced()) + .toList(); + + if (poolimeBlocks.isEmpty() || random.nextFloat() >= 0.65F) { + return; + } + + int spawnCount = Math.min(poolimeBlocks.size(), random.nextInt(2) + 1); + for (int index = 0; index < spawnCount; index++) { + BlockPos pos = poolimeBlocks.get(random.nextInt(poolimeBlocks.size())).pos().above(); + if (!box.isInside(pos)) { + continue; + } + + PoolimeEntity poolime = PEntityType.POOLIME.get().create(level.getLevel()); + if (poolime == null) { + continue; + } + + poolime.setSize(random.nextInt(3) + 1, true); + poolime.moveTo(pos.getX() + 0.5D, pos.getY(), pos.getZ() + 0.5D, random.nextFloat() * 360.0F, 0.0F); + if (level.noCollision(poolime, poolime.getBoundingBox())) { + level.addFreshEntity(poolime); + } + } + } + + private static void placePoopTree(WorldGenLevel level, RandomSource random, BlockPos basePos) { + int trunkHeight = random.nextIntBetweenInclusive(4, 5); + for (int y = 0; y < trunkHeight; y++) { + placeTreeBlock(level, basePos.above(y), PSBlocks.POOP_LOG.get().defaultBlockState()); + } + + BlockPos crown = basePos.above(trunkHeight); + for (int y = -2; y <= 1; y++) { + int radius = y == 1 ? 1 : 2; + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + if (Math.abs(x) == radius && Math.abs(z) == radius && random.nextBoolean()) { + continue; + } + + placeTreeBlock(level, crown.offset(x, y, z), PSBlocks.POOP_LEAVES.get().defaultBlockState()); + } + } + } + } + + private static void placeTreeBlock(WorldGenLevel level, BlockPos pos, BlockState state) { + if (level.isOutsideBuildHeight(pos)) { + return; + } + + if (level.getBlockState(pos).canBeReplaced()) { + level.setBlock(pos, state, 2); + } + } + + private static boolean isTreeMarker(StructureTemplate.StructureBlockInfo blockInfo) { + CompoundTag tag = blockInfo.nbt(); + return tag != null && PoopIslandStructure.POOP_TREE_MARKER.equals(tag.getString("metadata")); + } + + private static Rotation readRotation(CompoundTag tag) { + if (!tag.contains(ROTATION_KEY)) { + return Rotation.NONE; + } + + try { + return Rotation.valueOf(tag.getString(ROTATION_KEY)); + } catch (IllegalArgumentException exception) { + return Rotation.NONE; + } + } +} diff --git a/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java new file mode 100644 index 00000000..27358fd1 --- /dev/null +++ b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java @@ -0,0 +1,146 @@ +package com.altnoir.poopsky.worldgen.structure; + +import com.altnoir.poopsky.PoopSky; +import com.altnoir.poopsky.worldgen.PSStructures; +import com.mojang.serialization.MapCodec; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.SectionPos; +import net.minecraft.core.Vec3i; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.XoroshiroRandomSource; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.levelgen.structure.StructureType; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; + +import java.util.List; +import java.util.Optional; + +public class PoopIslandStructure extends Structure { + public static final MapCodec CODEC = simpleCodec(PoopIslandStructure::new); + public static final List DIRT_ISLAND_TEMPLATES = List.of( + PoopSky.loc("islands/dirt/0x1x0"), + PoopSky.loc("islands/dirt/11x1x11"), + PoopSky.loc("islands/dirt/2x2x4"), + PoopSky.loc("islands/dirt/4x1x6"), + PoopSky.loc("islands/dirt/8x1x11") + ); + public static final String POOP_TREE_MARKER = "poopsky:poop_tree"; + private static final int OVERWORLD_HEIGHT_OFFSET = 64; + private static final int VOID_ISLAND_Y = 128; + private static final int SPAWN_ISLAND_MIN_DISTANCE = 100; + private static final int SPAWN_ISLAND_MAX_DISTANCE = 200; + + public PoopIslandStructure(StructureSettings settings) { + super(settings); + } + + @Override + protected Optional findGenerationPoint(GenerationContext context) { + ResourceLocation templateId = randomTemplate(context.random()); + Optional template = context.structureTemplateManager().get(templateId); + if (template.isEmpty()) { + PoopSky.LOGGER.warn("Missing poop island template {}", templateId); + return Optional.empty(); + } + + Rotation rotation = Rotation.getRandom(context.random()); + ChunkPos chunkPos = context.chunkPos(); + int centerX = chunkPos.getMiddleBlockX(); + int centerZ = chunkPos.getMiddleBlockZ(); + int y = clampIslandY(context.heightAccessor(), + context.chunkGenerator().getFirstOccupiedHeight(centerX, centerZ, Heightmap.Types.WORLD_SURFACE_WG, context.heightAccessor(), context.randomState()) + + OVERWORLD_HEIGHT_OFFSET); + Vec3i size = template.get().getSize(rotation); + BlockPos origin = new BlockPos(centerX - size.getX() / 2, y, centerZ - size.getZ() / 2); + + return Optional.of(new GenerationStub(origin, builder -> + builder.addPiece(new PoopIslandPiece(context.structureTemplateManager(), templateId, origin, rotation)))); + } + + @Override + public StructureType type() { + return PSStructures.POOP_ISLAND.get(); + } + + public static void addGuaranteedSpawnStart( + RegistryAccess registries, + ChunkAccess chunk, + StructureManager structureManager, + StructureTemplateManager templateManager, + long seed + ) { + BlockPos center = getGuaranteedSpawnIslandCenter(seed); + if (!chunk.getPos().equals(new ChunkPos(center))) { + return; + } + + Registry structureRegistry = registries.registryOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.get(PoopSky.loc("poop_island")); + if (!(structure instanceof PoopIslandStructure)) { + PoopSky.LOGGER.warn("Missing poop island structure"); + return; + } + + SectionPos sectionPos = SectionPos.bottomOf(chunk); + StructureStart existingStart = structureManager.getStartForStructure(sectionPos, structure, chunk); + if (existingStart != null && existingStart.isValid()) { + return; + } + + int islandY = clampIslandY(chunk.getHeightAccessorForGeneration(), VOID_ISLAND_Y); + RandomSource random = RandomSource.create(seed ^ Mth.getSeed(center)); + ResourceLocation templateId = randomTemplate(random); + StructureTemplate template = templateManager.get(templateId).orElse(null); + if (template == null) { + PoopSky.LOGGER.warn("Missing poop island template {}", templateId); + return; + } + + Rotation rotation = Rotation.getRandom(random); + Vec3i size = template.getSize(rotation); + BlockPos origin = new BlockPos(center.getX() - size.getX() / 2, islandY, center.getZ() - size.getZ() / 2); + StructurePiecesBuilder builder = new StructurePiecesBuilder(); + builder.addPiece(new PoopIslandPiece(templateManager, templateId, origin, rotation)); + structureManager.setStartForStructure(sectionPos, structure, new StructureStart(structure, chunk.getPos(), 0, builder.build()), chunk); + } + + private static BlockPos getGuaranteedSpawnIslandCenter(long seed) { + RandomSource spawnRandom = new XoroshiroRandomSource(seed); + BlockPos spawn = new BlockPos( + spawnRandom.nextIntBetweenInclusive(-200, 200), + 87, + spawnRandom.nextIntBetweenInclusive(-200, 200) + ); + RandomSource random = RandomSource.create(seed ^ Mth.getSeed(spawn)); + int distance = random.nextIntBetweenInclusive(SPAWN_ISLAND_MIN_DISTANCE, SPAWN_ISLAND_MAX_DISTANCE); + double angle = random.nextDouble() * Math.TAU; + + return new BlockPos( + spawn.getX() + (int) Math.round(Math.cos(angle) * distance), + 0, + spawn.getZ() + (int) Math.round(Math.sin(angle) * distance) + ); + } + + private static ResourceLocation randomTemplate(RandomSource random) { + return DIRT_ISLAND_TEMPLATES.get(random.nextInt(DIRT_ISLAND_TEMPLATES.size())); + } + + private static int clampIslandY(LevelHeightAccessor level, int y) { + return Math.clamp(y, level.getMinBuildHeight() + 8, level.getMaxBuildHeight() - 32); + } +} diff --git a/src/main/resources/data/poopsky/structure/islands/dirt/0x1x0.nbt b/src/main/resources/data/poopsky/structure/islands/dirt/0x1x0.nbt new file mode 100644 index 0000000000000000000000000000000000000000..e1e6ba55318c28526dfaa0712c4c7f98e1f1ddc2 GIT binary patch literal 5611 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 MFOi~YJ1FG;068EX6951J literal 0 HcmV?d00001 diff --git a/src/main/resources/data/poopsky/structure/islands/dirt/11x1x11.nbt b/src/main/resources/data/poopsky/structure/islands/dirt/11x1x11.nbt new file mode 100644 index 0000000000000000000000000000000000000000..56092d683e9ddb65f0c63d3a73f14959d01ed69d GIT binary patch literal 13352 zcmeI3d011|w!le35*Z{{5s<;8URu3Y0V^ONA;D|a*D7$Wt)zkw#0v<@6oyCwBn0K_ zXBpaBhlqq*Tc`yI)B(bj6L|t&hTx416#@qtQe;RpLJ~vBI|18@g3{VQ-XHh#e802y zI(wge&Sqzywf0)#PA^+_dty^KT>IX8ROsJ(_K}C<^v1<|vTZ`y;zxho=-~5yy(Z<( zpwgBqwhm@o_`1K$;#B{8#!mO_Dl=ApaOQ)l&qELNKJ~l!UVsXu0;>^Hb607*CVRdI>U3OI8jB-Y* z9dVJ?sv7!cp7ilG;>4&_ts*fywHH65GEME!{=`XEK(%F;M-Dy3_10(}O;%x|0{Gcg znnbR)e=;%AVP=w(^1X(b@^h7oR+Al7t9eqG0NrV7QasTeMWD+IIT!h<^19JJzIvFS ztZY$Zq%%r2CY7#u>LXNzHmG?6DlJHoO1)Dj_dVqV@M+rNaZF;OO8enNg0yd1KB~^% zp&XH>PCk;36{e=f-5!NrNHy~5M9R~RJooA8zetH$MKjzxVyO2%;>l9k3((ymUH)Q3|rW;`DBBeVAM@O@d!6; zJ7b+&+e137s~W@-odeW+K+XD=3W2&K#!TjqY%XP}@ZksB;;b)}4sljy)ui8`-gC-7 zy4|*+J?co4d7zmM4cQRDba|Jg*zL8PjdJpn4B18910_}g6a<%d(oKP3s~p*|j>C+- z)O&k5cR$Yww#9eqr}Pqsox--Xt;*l9egE-XAF0b~JV03k^0Kle0d2=F^_62K`@Uio z=y~0Uv3qpQ+-XCYcm5qu(J}w-X?#?he%Q*kT+i9-25dv8f%?fV4}V*GFAO_q z;J#8qS*7b{J2b%h(1@U%?81(mvJD%~jF`M?voXl-Lm9;&V&AQUXG+cA86+8uesU}6 zYrxy!!aq_3Np{v!hm=}-gcqE>%4vPu5#4Rl3gEMYBCl6-uUd8hJC`?nQWsGo5H2s? z35^cZ&1G|)`LyU3? zt-S-dpi{fg_piv(ENe(f4<@FiVx2w0PGt($CR$7W&2U3Ey8#(6f-&epwu7%WmIcuT zxeb{@-$ZN9rFEs|2E{w!c|K&jm88@9T_2EZR+87N(%gKkIikpzFK#7$R~oRPc&Fjm zx4N^h;po?Msw0OCa|-~nVekJ4tP2}Pt=q=7Q}n$U>tM5!9GbZKS3E_m5dF;Jk?Kc3 zsXHn|OD8OgX{ZZ1FsWtnQB(w5zz!)bu$1LN<=k^P=QUwTy6Ld}+A(@V*-)8HtC!p0 zE|Uv6tcF+_$pqUZ2zH}xLTd*>j?MH}tkhVUkBQ~iJ9Z6$WpMlf&zV&q2R^-@k!mRO zfz50Hh)C*Xy{J`SCcJtd!^Kdx%V0F%mZ~US?rWghe=B~E0b!FM;~i?Te)iX3*-HF@ zTPI4>Z8i#uAeF8TVG|$7@auDoRF%TBKLN`O@dua1J>on<5>wg?C|qZX&(i)YWL zY>I-!5hR6G?B!F=SA)DFNL;IqA`~iuEZcW2lHO41EJ0O-kz1EjYnhf?H`k%6?=yU@ z&r=YT$hD~lWh5iVWBv5IX$=Qmr_8fzSfiOH^e!gGoy=poC2p3ORNrU%x;IKp9`g{w zL{Mc?eV6I7O|}aoJxP=KU{oTOOA<+h?YN%dJkES}h9)S93o98#kwh>Omh6Rw&Qu&( zjHVJ@0k%nL0Yc^sBxRPG+sd48sUp&wZJ3u<{1};jW3@nT+k7HTvB8uY59fM=?FK*o z6OQu+>!33#su@rlJLb;K>E2LTmoX(TsMWFg1T*4WWUr}RX@F62Cw%k0WB8H zkgf`CG)56H(cjzT(L>|i2tbp#h~iFI$Ee2%CMK!c`Uea1FADSgZ% z%_R%Ww51L#tHue4yJf-^(-eKJjlg<0S+#5vP7u6X*7e8;zx8;Jgx9Bxe6a5tx@CnE zjIIA{*?YDgSvf6#Hi>Pq7hNV|#Q(0-$Dd_1tdx=9Gamp%gCmFF*gZh7fpR@e`*u$J z+!^Ey8ckn2*0Ztd>-Y`O&nm!6P_%*SjN}&NWNc8nA&G^cWYu(T8fSlF^C~>&@7Myc zeN{C(%~#|gdsrERkqK$m%n&r&Bg`bqjvVO z;9t@9u`;@e8;8ZA$S5W$tpcH&(hW_NNkrqbvy8Gl1^P-jD|skx2iR^^eURlFAq!s~a2y~mKXM4s z8}?R%6-j~Ws9{bYC3{(uxTuaI=43dz=3rx{dF$79#WW+{-M>d(2Q$rMFQ4oQ69n5) zi;>wka_sDm>@n)hu=B^=1SRHMen1M#L6teaFHPd^%1Horq9r1e-plcTFPa_?c%y&n zhPkgP6}K}&_V^M!s7etRNrJ!wqsHz1VRrb4^32n66|!(j#CP-9b0{$ zAa|Q&~ARx-vHlT3hI@Ow9Vm zc7w;!Y4}_q!&DhPx7N^Iw}*J=#6MSNVnQ1^mXAYG+GdbrIqk?w4Uq|rXD$ecj>^ze z#TVC(hRFJj3Aus{=z9yTJ0QM-fw7SF!wLRChLJKFhO-CDkocR726I^#9P0-J8&QwJ zw8dbiS#?>O%K=%vVJ}Hwy^$Jx^7DgpXK2L|`;ru9KMom3#GJa}T6Zv)DR6978B+)d zRw(K8ISjw(1l$Y7wsAs+ec1MK{*~<_MuE5s2bO+em;LMN@T(c*3>kloN%8q3+Z~UM z?TqfKi2R9}ngDXvC_~XRuM));%Fv!J>;BS5y>twrhlNpqbsoy-#l?mxuuNk?J$AoK z4mEQ753D*#kBbG0(6W9QovMe!0vRuf3_PCUivima)JYjK;F8N!mg_P4`gnaIP9Wbc zn_huWO{Wv!7Q4X*%lvJar){HS^zE~N+bb$uUsgUcsvvEhs&YNn^OY&!tLF|I(=Rmz zyq6--VeX^#0XRSUJAf}-!qs2Bmmzp1E(-v8N0SHmWW!0sHr~B(QlIkh&6d0(E+VYg$-3W52RM+kj-L%lk6EouLan1NKcBC z1-y(>f4!!6O8wz)fLpvO{oXxb4ZL^bp8!;E1XweNvk_z->TCpf9p(KZ$c3nc@cFQk z(Ghu3fE=>IU_zJZnQ+Ew$SQjQ;B-5nDq_lg5yo2q@(VDMaLro-?Mi##O?Lj=n*hzI zy8Mp-U7ZNf7C1dS}t(6SL6w-VMgh136kBwfkk%?gAt9=L4RwjvNXBJPfyA=bGvTwCa!T zeHjw2fs)Ob59Vphiu3)sTM+J!rH)I75j6!SbbzcK za|Ogunb2~Y@|?(Yt^wEqWG{!(0t=GJsCWd~@dO@`G^?(qc3m4;z<#@0b0!<|=5{~t zzW!o3FAqD|*2td9xAb(399vKCslZmDv&F222|=&|EThS~Jg8$>d;(@+wHArJxOK{g z5Lqo0Gkp`m_BGUabik6tK(PHpu=K$|z2pK-HjTye(L$*}zI2ff$eZf1jSZR7MiZbF z*5#(>jwVa=#%vgu5Tz8oge54U^*%nBGdNK1f!$4(Ei=Y41-6^1`lxIGT(KGtXDwBk z0%JkT_StM}KI<<)tX>mr6S~yqcM5b^)45Cyr=04;Q0TuYR#APJiZ_OcHH-`kNE^mF zx$8W1y?JiSJi`RqqtZF-dM(S?22PG)=559KZmeBf`rGSkJN$TGY>zyWV{JLO3-#xS zgE`hZ_n!B~j>tttl_~Gh14}OhHc^;)P8b$W_0Uc9IGy>#2+?oRXInZMg&k1HbAe`^o|nYl!t2pBcW)_5?FM(v`A0d{uNBt}?n3kK zZjX%1v0g-^*S_-d%k7`!7$j&*M?8Ut#*YRsD=8&EH+eq;CmkM%db34{P> zY~1?CU=Osy$2R?L6oV==JU>n1E1NdV3lzvVQd5vZ?rW5m)k!FBo}egH_Hb!p01#{; z>q6oaVA+RWC)T7nCnF=C8g@J}D<0WM68V-EaLHbQ0(R}(g$F^L|9x)vYHo|!F6?Gy zF#IXwwMU#64cZ6&>0!9R&j`ccb9|J2Jib`}2@LuBk1M|AmHS@%WUr`jPwsbV9W2@N z4Ud1f9W*nOvG5AW3vbiKV?@jX zbc-H)g_CJmNZY3TPq$N0EeDqLqHX5V4YE9fKuuv=1^6uJZ-%xVi%(t3U;cJI;p6p< z>83|+8Eol)^8Pw};Zd++!B48~h0^vV44t*Q^Ph{E1wWbcVzi%!q&HmfbYz9VS`68a zVfN6(e$A|Y>f{oDzdRcldFEc*&@gP6lWo|#w&wg#J+Gw=8?%kVZf;A0zw)>_>-MhR z1kw-cv5=cd3*(*5Bsgn9_-CB+*{pnQ>2>X{gK68EH$Se$V~aud&9sR7hNKe4(5t{4 zvLcl%gnstTQ$lvQviJ(*_0GD!uK?_Z%hV_0iGj}cNNdOQEW$A3rQ|Jewrj#1+ z)_2A$e4f1jHm5o;#rsXs+_Qo#`@}2dH?|O)LSvmAr{?8Ql)rJ3P z&p(dp%n;gpI&Zbb^4m-8vB@=)k zQNi(n42hA94$E@@px&oQ*lohgH`V=B1a>pHz&p4+p{UowxY~2t3X2H6UNo5e#?bHG4JN-a()uj&7()i6KMGKBGPKJj7=;QY zNB@4DN_I5w;vR)1n3F?)7nmCMfv$5=ulKR%d*f3>?sX}JOZK;QzIm7Q30%E3klZ(H zG}cXjmE+x2u6kC}x0L^MV^^TYY>Yc&IOUs)R2T8nH0^g4R@oACi zs6Am~*%*|=V_QnmPsjpVS9H0--FK5EuU9@Y>iX1Ws@B_OVvhlNGee+T1T?UJ={UsN=x2xMt3@s3*(J^p)Z{W5;g)7WrJ@`Q1KbLVD zQl{k+nGzpRWlEjV&wD|Hl3AMSQ=0sq&w}|a9|mkUu4XfR9cAIDfEJ+0lIo1$hRiBi zz7$!wNkEIB$c^fZ<_?4HHq~sMGRTC`EXZ(EZbsvV!8#lIc@_pz!U&sy45YGD4`&Y6 z!RaIh#^_bbca)|2xU*p0N;)YGvqu&*m!_sHrQZs6fO0csuFq{B{4a6Xn4^pJ2v)N-6$_gAYVhRol9`T_c0~zGCDd>3h0!y2b39T2F|9V_D_B+ zbA>D_>LQ!>Tc@Ub9$ZIz)4=&q5=t`Eo=FvfMH#epG%_zk{eiB4J`2eU0}aOMdA|u> z43EALEq#4)JvkbV`hy51hoTZfemBgQ7#W=xL;W=}x?s5UC8Z;E%oyv=YG?(^UMtRz zj?NF7I;AQ#gGAz#*pa=`sXf%`HftT)3kJ!LlO95*H?`%R>fx0E6ay|Vz0+SXdIf~0 zPSQj4Y{CwQ*0LP$ix9R$rm@WRj;Z)jK(J&=KM!8o7dAgs>A?>D zU3*-obSc`;M8m9_v|ujSwb6Cz7cm0-=9KE*W{%bH{yKX2d7tI-D?x+R#^{QEut2*7Tpt zsOEPk({E^1=Qv%-+NthN=)VO#ZPF^w@u#(XjglWY{p8=e+JC z_cL`>F1VSIzG-20Wz~S1Pg7_2Ri99ZY&EhL1vfRR@my4G%|*Ued5@o}Xh~I5!-%+k zTwl%1xU`n0R`XM-Kl3$1|4N;aa&YR~hg70q&BQn^RXNpyNmYuwwU3%OMB`|!!!_B2 zL@qd95=blIkMUA2j&|los0-y?kcLu{kx_G_s@tFc(7z#rHxWhA{6M*c9ZqUua@{*c zb$NrMP_6iq+jah!a$ZV1Z!3o4(L4O~6G>9a`BCb{QQu3W;=11Mq1jpv*x_wGx{FUM ze5c2&C*viRspm(>w~darI-fW{dK&8H>E!4m39Ghc(u$&)Elbi}_yJKRF8mZRZwBh- zTDL!UOXt(icqQ7yCAeDEL{;BRzt~0E*H=&^HSreGKa9ROp&gA&#E6pLHGPQdtJOTx z^l3#=DNhwq#A;e{6U%{p^aN$htgySUfcBlx*jw1MA8OOdj*P!J0cm>*XtzgtOX^DE zqnbx9NX;WVgS3f`9)xD6$gG+Naq{jsuB&)zt9avH7w8iIR$LS(gU zm5KVHV@>j5O3O$|`A9%ar37lQS^OlZvTC;F!0}4o+zGm_E1G-Jp;4FUNFHr`WZi7t za-aH=_rzmU7gSJ_iP=6<&rdYXHq^{Egr3x_unKy{@A@2ghok|Na~zQ)f6WQq3rvhV z{utq{NDy{87t(mizN+?+gW}eI>K@UT*O@ypZv7c_ITpG@fnQ%qUCPN3?`OSQl(>og zCITX&szgF<^2LeC0-8Ec!xq&fZ%Jqw>8&-W9gd5VrgLww+QzY8K(CoV)IxpGTw*du z63S`sL$~TB+~z@*e2?flDo)58nJTYpfb@BhB52g2XxHeP3%#{pRMf|1_}!l|6xAFW z3ZPU@+8yUV-K!fxN@aPK4Yxg#sx}$FFRB@TpzAmR!#hBfjK8aZG|Bg=I!#O!)XQA` zzQcS-PJ9#?Tq&=MJ3Tu}-4bKn1LT!O>`mlphWns#>~^k$o^{VDNK;dDvEo6Tuzcf> VfkBm%VzCw#ci~+_S2i=xk)D+qhA;RIuJUPW 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/poopsky/worldgen/structure/poop_island.json b/src/main/resources/data/poopsky/worldgen/structure/poop_island.json new file mode 100644 index 00000000..8762e6b4 --- /dev/null +++ b/src/main/resources/data/poopsky/worldgen/structure/poop_island.json @@ -0,0 +1,7 @@ +{ + "type": "poopsky:poop_island", + "biomes": "#minecraft:is_overworld", + "spawn_overrides": {}, + "step": "surface_structures", + "terrain_adaptation": "none" +} diff --git a/src/main/resources/data/poopsky/worldgen/structure_set/poop_islands.json b/src/main/resources/data/poopsky/worldgen/structure_set/poop_islands.json new file mode 100644 index 00000000..50b6afe4 --- /dev/null +++ b/src/main/resources/data/poopsky/worldgen/structure_set/poop_islands.json @@ -0,0 +1,14 @@ +{ + "placement": { + "type": "minecraft:random_spread", + "salt": 70071103, + "separation": 12, + "spacing": 40 + }, + "structures": [ + { + "structure": "poopsky:poop_island", + "weight": 1 + } + ] +} diff --git a/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json b/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json index 60c53725..2353766c 100644 --- a/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json +++ b/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json @@ -11,7 +11,8 @@ "settings": "minecraft:overworld", "allowed_structure_sets": [ "minecraft:villages", - "minecraft:strongholds" + "minecraft:strongholds", + "poopsky:poop_island" ] } }, From d4ea4e766244eff13c6228eb1cf161bc1d48deb7 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sat, 13 Jun 2026 21:53:13 +0800 Subject: [PATCH 2/6] fix --- .../worldgen/PSVoidChunkGenerator.java | 24 ++++++++++++++++++- .../worldgen/world_preset/poopsky.json | 4 +++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java b/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java index 94cfc641..c9b5e4fe 100644 --- a/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java +++ b/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java @@ -3,10 +3,12 @@ import com.altnoir.poopsky.Config; import com.altnoir.poopsky.PoopSky; import com.altnoir.poopsky.worldgen.structure.PoopIslandStructure; +import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.server.level.ServerLevel; import net.minecraft.CrashReport; import net.minecraft.ReportedException; import net.minecraft.SharedConstants; @@ -62,6 +64,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; +import javax.annotation.Nullable; public class PSVoidChunkGenerator extends NoiseBasedChunkGenerator { private static final int VIRTUAL_SURFACE_Y = 64; @@ -227,6 +230,25 @@ public void createStructures( } } + @Override + @Nullable + public Pair> findNearestMapStructure(ServerLevel level, HolderSet structures, BlockPos pos, int searchRadius, boolean skipKnownStructures) { + if (generateNormal || allowedStructureSets.isEmpty()) { + return super.findNearestMapStructure(level, structures, pos, searchRadius, skipKnownStructures); + } + + Set allowedStructures = resolveAllowedStructures(level.registryAccess()); + List> searchableStructures = structures.stream() + .filter(structure -> allowedStructures.contains(structure.value())) + .toList(); + + if (searchableStructures.isEmpty()) { + return null; + } + + return super.findNearestMapStructure(level, HolderSet.direct(searchableStructures), pos, searchRadius, skipKnownStructures); + } + @Override public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager ) { @@ -455,4 +477,4 @@ private static BoundingBox writableArea(ChunkAccess chunk) { private static int virtualSurfaceY(LevelHeightAccessor level) { return Math.clamp(VIRTUAL_SURFACE_Y, level.getMinBuildHeight() + 1, level.getMaxBuildHeight()); } -} \ No newline at end of file +} diff --git a/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json b/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json index 2353766c..dc3bf09e 100644 --- a/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json +++ b/src/main/resources/data/poopsky/worldgen/world_preset/poopsky.json @@ -10,7 +10,9 @@ }, "settings": "minecraft:overworld", "allowed_structure_sets": [ - "minecraft:villages", + "minecraft:pillager_outpost", + "minecraft:swamp_hut", + "minecraft:monument", "minecraft:strongholds", "poopsky:poop_island" ] From ddb28d82ed6917972c75695a2ac2f57200ba4472 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sun, 14 Jun 2026 12:58:31 +0800 Subject: [PATCH 3/6] config --- src/main/java/com/altnoir/poopsky/Config.java | 7 +----- .../mixin/NoiseBasedChunkGeneratorMixin.java | 22 ------------------- .../resources/assets/poopsky/lang/en_us.json | 2 -- .../resources/assets/poopsky/lang/zh_cn.json | 2 -- src/main/resources/poopsky.mixins.json | 1 - 5 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java diff --git a/src/main/java/com/altnoir/poopsky/Config.java b/src/main/java/com/altnoir/poopsky/Config.java index abbdea33..e00ed682 100644 --- a/src/main/java/com/altnoir/poopsky/Config.java +++ b/src/main/java/com/altnoir/poopsky/Config.java @@ -19,7 +19,7 @@ public class Config { private static final ModConfigSpec.BooleanValue SET_POOPSKY_DEFAULT = BUILDER .comment("Whether the dedicated server level-type default should be set to poopsky") .translation("poopsky.configuration.setPoopskyDefault") - .define("setPoopskyDefault", false); + .define("setPoopskyDefault", true); private static final ModConfigSpec.BooleanValue VOID_NETHER_GENERATION = BUILDER .comment("Whether the custom void generator should also keep the nether empty") .translation("poopsky.configuration.voidNetherGeneration") @@ -32,10 +32,6 @@ public class Config { .comment("Whether to Disable the consumption of liquid when sticks crafting") .translation("poopsky.configuration.stickyCrafting") .define("stickyCrafting", false); - private static final ModConfigSpec.BooleanValue LAVA_FLUID_BLOCK = BUILDER - .comment("Whether to Disable the underground lava lake") - .translation("poopsky.configuration.lavaFluid") - .define("lavaFluid", true); private static final ModConfigSpec.BooleanValue PLUG_TRADES = BUILDER .comment("Whether to Disable the plug trades") .translation("poopsky.configuration.plugTrades") @@ -49,7 +45,6 @@ static void onLoad(final ModConfigEvent event) { voidNetherGeneration = VOID_NETHER_GENERATION.get(); desperateWorld = DESPERATE_WORLD.get(); stickyCrafting = STICK_CRAFTING.get(); - lavaFluid = LAVA_FLUID_BLOCK.get(); plugTrades = PLUG_TRADES.get(); } } diff --git a/src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java b/src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java deleted file mode 100644 index 7ecc5ff9..00000000 --- a/src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.altnoir.poopsky.mixin; - -import com.altnoir.poopsky.Config; -import net.minecraft.world.level.levelgen.Aquifer; -import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; -import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; -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.CallbackInfoReturnable; - -@Mixin(value = NoiseBasedChunkGenerator.class) -public class NoiseBasedChunkGeneratorMixin { - @Inject(method = "createFluidPicker", at = @At("RETURN"), cancellable = true) - private static void injectCreateFluidPicker(NoiseGeneratorSettings settings, CallbackInfoReturnable cir) { - if (Config.lavaFluid) { - Aquifer.FluidStatus fluidStatus = new Aquifer.FluidStatus(settings.seaLevel(), settings.defaultFluid()); - Aquifer.FluidPicker modify = (x, y, z) -> fluidStatus; - cir.setReturnValue(modify); - } - } -} diff --git a/src/main/resources/assets/poopsky/lang/en_us.json b/src/main/resources/assets/poopsky/lang/en_us.json index 63264832..02e89999 100644 --- a/src/main/resources/assets/poopsky/lang/en_us.json +++ b/src/main/resources/assets/poopsky/lang/en_us.json @@ -277,8 +277,6 @@ "itemgroup.poopsky": "POOPSKY", "poopsky.configuration.title": "%s Options", - "poopsky.configuration.lavaFluid": "Disable Underground Lava Lakes", - "poopsky.configuration.lavaFluid.tooltip": "Disables underground lava lakes during PoopSky world generation", "poopsky.configuration.stickyCrafting": "Keep Liquid When Crafting Sticks", "poopsky.configuration.stickyCrafting.tooltip": "Prevents liquid in the compooper from being consumed when crafting sticks", "poopsky.configuration.desperateWorld": "Desperate World", diff --git a/src/main/resources/assets/poopsky/lang/zh_cn.json b/src/main/resources/assets/poopsky/lang/zh_cn.json index 68e80ee3..cb5f6214 100644 --- a/src/main/resources/assets/poopsky/lang/zh_cn.json +++ b/src/main/resources/assets/poopsky/lang/zh_cn.json @@ -277,8 +277,6 @@ "itemgroup.poopsky": "空中厕所", "poopsky.configuration.title": "%s 配置", - "poopsky.configuration.lavaFluid": "禁用地下熔岩湖", - "poopsky.configuration.lavaFluid.tooltip": "在空中厕所世界生成中禁用地下熔岩湖", "poopsky.configuration.stickyCrafting": "合成木棍时保留液体", "poopsky.configuration.stickyCrafting.tooltip": "使用堆粪桶合成木棍时不消耗桶内液体", "poopsky.configuration.desperateWorld": "绝望世界", diff --git a/src/main/resources/poopsky.mixins.json b/src/main/resources/poopsky.mixins.json index 3c391dfa..7ade175a 100644 --- a/src/main/resources/poopsky.mixins.json +++ b/src/main/resources/poopsky.mixins.json @@ -7,7 +7,6 @@ "BaseCoralPlantTypeBlockMixin", "CarvedPumpkinBlockMixin", "FishingHookMixin", - "NoiseBasedChunkGeneratorMixin", "TradeWithVillagerMixin", "VillagerMixin" ], From 00f218fb966cf5c74bed853fe8eab6189c67066b Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sun, 14 Jun 2026 13:42:12 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E7=A9=BA=E5=B2=9B=E5=88=B7=E5=B1=8E?= =?UTF-8?q?=E8=8E=B1=E5=A7=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../poopsky/entity/p/PoolimeEntity.java | 72 ++++++++++++++-- .../altnoir/poopsky/init/PSoundEvents.java | 9 ++ .../worldgen/structure/PoopIslandPiece.java | 16 ++-- .../resources/assets/poopsky/lang/en_us.json | 4 + .../resources/assets/poopsky/lang/zh_cn.json | 4 + src/main/resources/assets/poopsky/sounds.json | 83 ++++++++++++++++++- .../worldgen/structure/poop_island.json | 14 +++- 7 files changed, 185 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/altnoir/poopsky/entity/p/PoolimeEntity.java b/src/main/java/com/altnoir/poopsky/entity/p/PoolimeEntity.java index 5fa367b1..85860a29 100644 --- a/src/main/java/com/altnoir/poopsky/entity/p/PoolimeEntity.java +++ b/src/main/java/com/altnoir/poopsky/entity/p/PoolimeEntity.java @@ -1,17 +1,28 @@ package com.altnoir.poopsky.entity.p; +import com.altnoir.poopsky.PoopSky; import com.altnoir.poopsky.block.PSBlocks; import com.altnoir.poopsky.init.PParticles; +import com.altnoir.poopsky.init.PSoundEvents; +import net.minecraft.core.registries.Registries; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; import net.minecraft.util.RandomSource; +import net.minecraft.world.Difficulty; +import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.entity.monster.Slime; +import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.levelgen.structure.Structure; import org.jetbrains.annotations.NotNull; public class PoolimeEntity extends Slime { @@ -28,10 +39,59 @@ public static AttributeSupplier.Builder createAttributes() { return PParticles.POOP_PARTICLE.get(); } - public static boolean checkPoolimeSpawnRules( - EntityType poolime, LevelAccessor level, MobSpawnType spawnType, BlockPos pos, RandomSource random - ) { - boolean flag = MobSpawnType.ignoresLightRequirements(spawnType); - return level.getBlockState(pos.below()).is(PSBlocks.POOLIME_POOP_BLOCK.get()) && flag; + @Override + protected SoundEvent getHurtSound(DamageSource damageSource) { + return this.isTiny() ? PSoundEvents.ENTITY_POOLIME_HURT_SMALL.get() : PSoundEvents.ENTITY_POOLIME_HURT.get(); + } + + @Override + protected SoundEvent getDeathSound() { + return this.isTiny() ? PSoundEvents.ENTITY_POOLIME_DEATH_SMALL.get() : PSoundEvents.ENTITY_POOLIME_DEATH.get(); + } + + @Override + protected SoundEvent getSquishSound() { + return this.isTiny() ? PSoundEvents.ENTITY_POOLIME_SQUISH_SMALL.get() : PSoundEvents.ENTITY_POOLIME_SQUISH.get(); + } + + @Override + protected SoundEvent getJumpSound() { + return this.isTiny() ? PSoundEvents.ENTITY_POOLIME_JUMP_SMALL.get() : PSoundEvents.ENTITY_POOLIME_JUMP.get(); + } + + @Override + protected void dealDamage(LivingEntity livingEntity) { + if (this.isAlive() && this.isWithinMeleeAttackRange(livingEntity) && this.hasLineOfSight(livingEntity)) { + DamageSource damageSource = this.damageSources().mobAttack(this); + if (livingEntity.hurt(damageSource, this.getAttackDamage())) { + this.playSound(PSoundEvents.ENTITY_POOLIME_ATTACK.get(), 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); + if (this.level() instanceof ServerLevel serverLevel) { + EnchantmentHelper.doPostAttackEffects(serverLevel, livingEntity, damageSource); + } + } + } + } + + public static boolean checkPoolimeSpawnRules(EntityType poolime, LevelAccessor level, MobSpawnType spawnType, BlockPos pos, RandomSource random) { + if (level.getDifficulty() == Difficulty.PEACEFUL || !Mob.checkMobSpawnRules(poolime, level, spawnType, pos, random)) { + return false; + } + + if (MobSpawnType.ignoresLightRequirements(spawnType)) { + return true; + } + + return level.getBlockState(pos.below()).is(PSBlocks.POOLIME_POOP_BLOCK.get()) || isInPoopIsland(level, pos); + } + + private static boolean isInPoopIsland(LevelAccessor level, BlockPos pos) { + if (!(level instanceof ServerLevel serverLevel)) { + return false; + } + + Structure structure = serverLevel.registryAccess() + .registryOrThrow(Registries.STRUCTURE) + .get(PoopSky.loc("poop_island")); + return structure != null && serverLevel.structureManager().getStructureAt(pos, structure).isValid(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/altnoir/poopsky/init/PSoundEvents.java b/src/main/java/com/altnoir/poopsky/init/PSoundEvents.java index 2ee41c70..6f2dd6c0 100644 --- a/src/main/java/com/altnoir/poopsky/init/PSoundEvents.java +++ b/src/main/java/com/altnoir/poopsky/init/PSoundEvents.java @@ -19,6 +19,15 @@ public class PSoundEvents { public static final Supplier BLOCK_COMPOOPER_MAGGOTS = registerSoundEvent("block.compooper.maggots"); public static final Supplier ENTITY_VILLAGER_WORK_COMPOOPER = registerSoundEvent("entity.villager.work_compooper"); public static final Supplier ENTITY_VILLAGER_WORK_TOILET = registerSoundEvent("entity.villager.work_toilet"); + public static final Supplier ENTITY_POOLIME_ATTACK = registerSoundEvent("entity.poolime.attack"); + public static final Supplier ENTITY_POOLIME_DEATH = registerSoundEvent("entity.poolime.death"); + public static final Supplier ENTITY_POOLIME_DEATH_SMALL = registerSoundEvent("entity.poolime.death_small"); + public static final Supplier ENTITY_POOLIME_HURT = registerSoundEvent("entity.poolime.hurt"); + public static final Supplier ENTITY_POOLIME_HURT_SMALL = registerSoundEvent("entity.poolime.hurt_small"); + public static final Supplier ENTITY_POOLIME_JUMP = registerSoundEvent("entity.poolime.jump"); + public static final Supplier ENTITY_POOLIME_JUMP_SMALL = registerSoundEvent("entity.poolime.jump_small"); + public static final Supplier ENTITY_POOLIME_SQUISH = registerSoundEvent("entity.poolime.squish"); + public static final Supplier ENTITY_POOLIME_SQUISH_SMALL = registerSoundEvent("entity.poolime.squish_small"); public static final Supplier LAWRENCE = registerSoundEvent("lawrence"); public static final ResourceKey LAWRENCE_KEY = registerJukeboxSong("lawrence"); diff --git a/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java index e4f953ea..774df4e8 100644 --- a/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java +++ b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandPiece.java @@ -25,6 +25,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import java.util.ArrayList; import java.util.List; public class PoopIslandPiece extends TemplateStructurePiece { @@ -87,22 +88,19 @@ public static void placeRandomPoopTree(WorldGenLevel level, RandomSource random, } public static void spawnRandomPoolimes(WorldGenLevel level, RandomSource random, StructureTemplate template, BlockPos origin, StructurePlaceSettings settings, BoundingBox box) { - List poolimeBlocks = template.filterBlocks(origin, settings, PSBlocks.POOLIME_BLOCK.get()) + List poolimeBlocks = new ArrayList<>(template.filterBlocks(origin, settings, PSBlocks.POOLIME_BLOCK.get()) .stream() + .filter(blockInfo -> box.isInside(blockInfo.pos().above())) .filter(blockInfo -> level.getBlockState(blockInfo.pos().above()).canBeReplaced()) - .toList(); + .toList()); - if (poolimeBlocks.isEmpty() || random.nextFloat() >= 0.65F) { + if (poolimeBlocks.isEmpty()) { return; } - int spawnCount = Math.min(poolimeBlocks.size(), random.nextInt(2) + 1); + int spawnCount = Math.min(poolimeBlocks.size(), random.nextIntBetweenInclusive(1, 3)); for (int index = 0; index < spawnCount; index++) { - BlockPos pos = poolimeBlocks.get(random.nextInt(poolimeBlocks.size())).pos().above(); - if (!box.isInside(pos)) { - continue; - } - + BlockPos pos = poolimeBlocks.remove(random.nextInt(poolimeBlocks.size())).pos().above(); PoolimeEntity poolime = PEntityType.POOLIME.get().create(level.getLevel()); if (poolime == null) { continue; diff --git a/src/main/resources/assets/poopsky/lang/en_us.json b/src/main/resources/assets/poopsky/lang/en_us.json index 02e89999..d94ae8ea 100644 --- a/src/main/resources/assets/poopsky/lang/en_us.json +++ b/src/main/resources/assets/poopsky/lang/en_us.json @@ -263,6 +263,10 @@ "subtitle.poopsky.compooper.maggots": "Compooper maggots", "subtitle.poopsky.villager.work_compooper": "Poopmaker works", "subtitle.poopsky.villager.work_toilet": "Gastronome eats", + "subtitle.poopsky.poolime.attack": "Poolime attacks", + "subtitle.poopsky.poolime.death": "Poolime dies", + "subtitle.poopsky.poolime.hurt": "Poolime hurts", + "subtitle.poopsky.poolime.squish": "Poolime squishes", "tag.item.poopsky.compooper_saplings": "Compooper Saplings", "jei.category.poopsky.compooper": "Compooper", diff --git a/src/main/resources/assets/poopsky/lang/zh_cn.json b/src/main/resources/assets/poopsky/lang/zh_cn.json index cb5f6214..dae70e07 100644 --- a/src/main/resources/assets/poopsky/lang/zh_cn.json +++ b/src/main/resources/assets/poopsky/lang/zh_cn.json @@ -263,6 +263,10 @@ "subtitle.poopsky.compooper.maggots": "堆粪桶:产蛆", "subtitle.poopsky.villager.work_compooper": "养粪人:工作", "subtitle.poopsky.villager.work_toilet": "美食家:进食", + "subtitle.poopsky.poolime.attack": "屎莱姆:攻击", + "subtitle.poopsky.poolime.death": "屎莱姆:死亡", + "subtitle.poopsky.poolime.hurt": "屎莱姆:受伤", + "subtitle.poopsky.poolime.squish": "屎莱姆:挤压", "tag.item.poopsky.compooper_saplings": "堆粪桶可产出树苗", "jei.category.poopsky.compooper": "堆粪桶", diff --git a/src/main/resources/assets/poopsky/sounds.json b/src/main/resources/assets/poopsky/sounds.json index bad9a642..d15ce32e 100644 --- a/src/main/resources/assets/poopsky/sounds.json +++ b/src/main/resources/assets/poopsky/sounds.json @@ -44,6 +44,87 @@ "random/burp" ] }, + "entity.poolime.attack": { + "subtitle": "subtitle.poopsky.poolime.attack", + "sounds": [ + { + "name": "minecraft:entity.slime.attack", + "type": "event" + } + ] + }, + "entity.poolime.death": { + "subtitle": "subtitle.poopsky.poolime.death", + "sounds": [ + { + "name": "minecraft:entity.slime.death", + "type": "event" + } + ] + }, + "entity.poolime.death_small": { + "subtitle": "subtitle.poopsky.poolime.death", + "sounds": [ + { + "name": "minecraft:entity.slime.death_small", + "type": "event" + } + ] + }, + "entity.poolime.hurt": { + "subtitle": "subtitle.poopsky.poolime.hurt", + "sounds": [ + { + "name": "minecraft:entity.slime.hurt", + "type": "event" + } + ] + }, + "entity.poolime.hurt_small": { + "subtitle": "subtitle.poopsky.poolime.hurt", + "sounds": [ + { + "name": "minecraft:entity.slime.hurt_small", + "type": "event" + } + ] + }, + "entity.poolime.jump": { + "subtitle": "subtitle.poopsky.poolime.squish", + "sounds": [ + { + "name": "minecraft:entity.slime.jump", + "type": "event" + } + ] + }, + "entity.poolime.jump_small": { + "subtitle": "subtitle.poopsky.poolime.squish", + "sounds": [ + { + "name": "minecraft:entity.slime.jump_small", + "type": "event" + } + ] + }, + "entity.poolime.squish": { + "subtitle": "subtitle.poopsky.poolime.squish", + "sounds": [ + { + "name": "minecraft:entity.slime.squish", + "type": "event" + } + ] + }, + "entity.poolime.squish_small": { + "subtitle": "subtitle.poopsky.poolime.squish", + "sounds": [ + { + "name": "minecraft:entity.slime.squish_small", + "type": "event" + } + ] + }, "lawrence": { "sounds": [ { @@ -68,4 +149,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/main/resources/data/poopsky/worldgen/structure/poop_island.json b/src/main/resources/data/poopsky/worldgen/structure/poop_island.json index 8762e6b4..d53743d1 100644 --- a/src/main/resources/data/poopsky/worldgen/structure/poop_island.json +++ b/src/main/resources/data/poopsky/worldgen/structure/poop_island.json @@ -1,7 +1,19 @@ { "type": "poopsky:poop_island", "biomes": "#minecraft:is_overworld", - "spawn_overrides": {}, + "spawn_overrides": { + "monster": { + "bounding_box": "full", + "spawns": [ + { + "type": "poopsky:poolime", + "weight": 100, + "minCount": 1, + "maxCount": 3 + } + ] + } + }, "step": "surface_structures", "terrain_adaptation": "none" } From b7f2178fb3cf35efcc394c15b5143d705501e640 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sun, 14 Jun 2026 13:48:14 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=92=A4=E5=9B=9E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/altnoir/poopsky/Config.java | 5 +++++ .../mixin/NoiseBasedChunkGeneratorMixin.java | 22 +++++++++++++++++++ .../resources/assets/poopsky/lang/en_us.json | 2 ++ .../resources/assets/poopsky/lang/zh_cn.json | 2 ++ src/main/resources/poopsky.mixins.json | 1 + 5 files changed, 32 insertions(+) create mode 100644 src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java diff --git a/src/main/java/com/altnoir/poopsky/Config.java b/src/main/java/com/altnoir/poopsky/Config.java index e00ed682..1cabf9dd 100644 --- a/src/main/java/com/altnoir/poopsky/Config.java +++ b/src/main/java/com/altnoir/poopsky/Config.java @@ -32,6 +32,10 @@ public class Config { .comment("Whether to Disable the consumption of liquid when sticks crafting") .translation("poopsky.configuration.stickyCrafting") .define("stickyCrafting", false); + private static final ModConfigSpec.BooleanValue LAVA_FLUID_BLOCK = BUILDER + .comment("Whether to Disable the underground lava lake") + .translation("poopsky.configuration.lavaFluid") + .define("lavaFluid", true); private static final ModConfigSpec.BooleanValue PLUG_TRADES = BUILDER .comment("Whether to Disable the plug trades") .translation("poopsky.configuration.plugTrades") @@ -45,6 +49,7 @@ static void onLoad(final ModConfigEvent event) { voidNetherGeneration = VOID_NETHER_GENERATION.get(); desperateWorld = DESPERATE_WORLD.get(); stickyCrafting = STICK_CRAFTING.get(); + lavaFluid = LAVA_FLUID_BLOCK.get(); plugTrades = PLUG_TRADES.get(); } } diff --git a/src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java b/src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java new file mode 100644 index 00000000..7ecc5ff9 --- /dev/null +++ b/src/main/java/com/altnoir/poopsky/mixin/NoiseBasedChunkGeneratorMixin.java @@ -0,0 +1,22 @@ +package com.altnoir.poopsky.mixin; + +import com.altnoir.poopsky.Config; +import net.minecraft.world.level.levelgen.Aquifer; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +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.CallbackInfoReturnable; + +@Mixin(value = NoiseBasedChunkGenerator.class) +public class NoiseBasedChunkGeneratorMixin { + @Inject(method = "createFluidPicker", at = @At("RETURN"), cancellable = true) + private static void injectCreateFluidPicker(NoiseGeneratorSettings settings, CallbackInfoReturnable cir) { + if (Config.lavaFluid) { + Aquifer.FluidStatus fluidStatus = new Aquifer.FluidStatus(settings.seaLevel(), settings.defaultFluid()); + Aquifer.FluidPicker modify = (x, y, z) -> fluidStatus; + cir.setReturnValue(modify); + } + } +} diff --git a/src/main/resources/assets/poopsky/lang/en_us.json b/src/main/resources/assets/poopsky/lang/en_us.json index d94ae8ea..c7570205 100644 --- a/src/main/resources/assets/poopsky/lang/en_us.json +++ b/src/main/resources/assets/poopsky/lang/en_us.json @@ -281,6 +281,8 @@ "itemgroup.poopsky": "POOPSKY", "poopsky.configuration.title": "%s Options", + "poopsky.configuration.lavaFluid": "Disable Underground Lava Lakes", + "poopsky.configuration.lavaFluid.tooltip": "Disables underground lava lakes during PoopSky world generation", "poopsky.configuration.stickyCrafting": "Keep Liquid When Crafting Sticks", "poopsky.configuration.stickyCrafting.tooltip": "Prevents liquid in the compooper from being consumed when crafting sticks", "poopsky.configuration.desperateWorld": "Desperate World", diff --git a/src/main/resources/assets/poopsky/lang/zh_cn.json b/src/main/resources/assets/poopsky/lang/zh_cn.json index dae70e07..cdbed34e 100644 --- a/src/main/resources/assets/poopsky/lang/zh_cn.json +++ b/src/main/resources/assets/poopsky/lang/zh_cn.json @@ -281,6 +281,8 @@ "itemgroup.poopsky": "空中厕所", "poopsky.configuration.title": "%s 配置", + "poopsky.configuration.lavaFluid": "禁用地下熔岩湖", + "poopsky.configuration.lavaFluid.tooltip": "在空中厕所世界生成中禁用地下熔岩湖", "poopsky.configuration.stickyCrafting": "合成木棍时保留液体", "poopsky.configuration.stickyCrafting.tooltip": "使用堆粪桶合成木棍时不消耗桶内液体", "poopsky.configuration.desperateWorld": "绝望世界", diff --git a/src/main/resources/poopsky.mixins.json b/src/main/resources/poopsky.mixins.json index 7ade175a..3c391dfa 100644 --- a/src/main/resources/poopsky.mixins.json +++ b/src/main/resources/poopsky.mixins.json @@ -7,6 +7,7 @@ "BaseCoralPlantTypeBlockMixin", "CarvedPumpkinBlockMixin", "FishingHookMixin", + "NoiseBasedChunkGeneratorMixin", "TradeWithVillagerMixin", "VillagerMixin" ], From e1262c032ccad76e3efcd70486959a9571f5e22a Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sun, 14 Jun 2026 17:55:43 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BF=9D=E5=BA=95?= =?UTF-8?q?=E5=B2=9B=E4=BD=BF=E7=94=A8=E4=BC=AA=E5=87=BA=E7=94=9F=E7=82=B9?= =?UTF-8?q?=E9=80=A0=E6=88=90=E8=B7=9D=E7=A6=BB=E4=B8=8D=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../altnoir/poopsky/event/PSGameEvents.java | 10 ++++- .../worldgen/PSVoidChunkGenerator.java | 43 +++++++++++++++++-- .../structure/PoopIslandStructure.java | 38 ++++++++++++---- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/altnoir/poopsky/event/PSGameEvents.java b/src/main/java/com/altnoir/poopsky/event/PSGameEvents.java index a8bfbd6b..4977eae6 100644 --- a/src/main/java/com/altnoir/poopsky/event/PSGameEvents.java +++ b/src/main/java/com/altnoir/poopsky/event/PSGameEvents.java @@ -12,6 +12,7 @@ import com.altnoir.poopsky.villager.PSVillagerBehaviors; import com.altnoir.poopsky.villager.PSVillagerTrades; import com.altnoir.poopsky.worldgen.PSVoidChunkGenerator; +import com.altnoir.poopsky.worldgen.structure.PoopIslandStructure; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; @@ -26,6 +27,7 @@ import net.minecraft.world.item.*; import net.minecraft.world.item.alchemy.PotionBrewing; import net.minecraft.world.item.alchemy.Potions; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; @@ -147,8 +149,14 @@ public static void createSpawnToilet(LevelEvent.CreateSpawnPosition event) { level.setBlock(pos, AllToiletBlocks.OAK_TOILET.get().defaultBlockState(), 2); event.setCanceled(true); - event.getSettings().setSpawn(level.getHeightmapPos(Heightmap.Types.WORLD_SURFACE_WG, pos), 90.0F); + BlockPos spawn = level.getHeightmapPos(Heightmap.Types.WORLD_SURFACE_WG, pos); + event.getSettings().setSpawn(spawn, 90.0F); level.getGameRules().getRule(GameRules.RULE_SPAWN_RADIUS).set(0, level.getServer()); + + PoopIslandStructure.registerGuaranteedSpawn(level.getSeed(), spawn); + BlockPos islandCenter = PoopIslandStructure.getGuaranteedSpawnIslandCenter(level.getSeed(), spawn); + ChunkPos islandChunk = new ChunkPos(islandCenter); + level.getChunk(islandChunk.x, islandChunk.z); } } } diff --git a/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java b/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java index c9b5e4fe..d563bd8e 100644 --- a/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java +++ b/src/main/java/com/altnoir/poopsky/worldgen/PSVoidChunkGenerator.java @@ -233,8 +233,9 @@ public void createStructures( @Override @Nullable public Pair> findNearestMapStructure(ServerLevel level, HolderSet structures, BlockPos pos, int searchRadius, boolean skipKnownStructures) { + Pair> guaranteedSpawnIsland = findGuaranteedSpawnIsland(level, structures, pos, searchRadius, skipKnownStructures); if (generateNormal || allowedStructureSets.isEmpty()) { - return super.findNearestMapStructure(level, structures, pos, searchRadius, skipKnownStructures); + return nearestStructure(pos, super.findNearestMapStructure(level, structures, pos, searchRadius, skipKnownStructures), guaranteedSpawnIsland); } Set allowedStructures = resolveAllowedStructures(level.registryAccess()); @@ -243,10 +244,10 @@ public Pair> findNearestMapStructure(ServerLevel lev .toList(); if (searchableStructures.isEmpty()) { - return null; + return guaranteedSpawnIsland; } - return super.findNearestMapStructure(level, HolderSet.direct(searchableStructures), pos, searchRadius, skipKnownStructures); + return nearestStructure(pos, super.findNearestMapStructure(level, HolderSet.direct(searchableStructures), pos, searchRadius, skipKnownStructures), guaranteedSpawnIsland); } @Override @@ -299,6 +300,42 @@ private boolean isStructureAllowed(RegistryAccess registries, ResourceLocation s return structure != null && resolveAllowedStructures(registries).contains(structure); } + @Nullable + private Pair> findGuaranteedSpawnIsland(ServerLevel level, HolderSet structures, BlockPos pos, int searchRadius, boolean skipKnownStructures) { + if (skipKnownStructures || generateNormal || !settings.is(ResourceLocation.parse("minecraft:overworld")) || !isStructureAllowed(level.registryAccess(), PoopSky.loc("poop_island"))) { + return null; + } + + Holder poopIsland = structures.stream() + .filter(structure -> structure.unwrapKey() + .map(key -> key.location().equals(PoopSky.loc("poop_island"))) + .orElse(false)) + .findFirst() + .orElse(null); + if (poopIsland == null) { + return null; + } + + BlockPos center = PoopIslandStructure.getGuaranteedSpawnIslandCenter(level); + int chunkDistance = Math.max( + Math.abs(SectionPos.blockToSectionCoord(pos.getX()) - SectionPos.blockToSectionCoord(center.getX())), + Math.abs(SectionPos.blockToSectionCoord(pos.getZ()) - SectionPos.blockToSectionCoord(center.getZ())) + ); + return chunkDistance <= searchRadius ? Pair.of(center, poopIsland) : null; + } + + @Nullable + private static Pair> nearestStructure(BlockPos pos, @Nullable Pair> first, @Nullable Pair> second) { + if (first == null) { + return second; + } + if (second == null) { + return first; + } + + return pos.distSqr(second.getFirst()) < pos.distSqr(first.getFirst()) ? second : first; + } + private void createAllowedStructures(RegistryAccess registries, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess chunk, StructureTemplateManager templateManager) { ChunkPos chunkPos = chunk.getPos(); SectionPos sectionPos = SectionPos.bottomOf(chunk); diff --git a/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java index 27358fd1..016d0b90 100644 --- a/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java +++ b/src/main/java/com/altnoir/poopsky/worldgen/structure/PoopIslandStructure.java @@ -10,6 +10,7 @@ import net.minecraft.core.Vec3i; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.level.ChunkPos; @@ -27,7 +28,9 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; public class PoopIslandStructure extends Structure { public static final MapCodec CODEC = simpleCodec(PoopIslandStructure::new); @@ -43,6 +46,7 @@ public class PoopIslandStructure extends Structure { private static final int VOID_ISLAND_Y = 128; private static final int SPAWN_ISLAND_MIN_DISTANCE = 100; private static final int SPAWN_ISLAND_MAX_DISTANCE = 200; + private static final Map GUARANTEED_SPAWNS = new ConcurrentHashMap<>(); public PoopIslandStructure(StructureSettings settings) { super(settings); @@ -83,7 +87,8 @@ public static void addGuaranteedSpawnStart( StructureTemplateManager templateManager, long seed ) { - BlockPos center = getGuaranteedSpawnIslandCenter(seed); + BlockPos registeredSpawn = GUARANTEED_SPAWNS.get(seed); + BlockPos center = registeredSpawn != null ? getGuaranteedSpawnIslandCenter(seed, registeredSpawn) : getGuaranteedSpawnIslandCenter(seed); if (!chunk.getPos().equals(new ChunkPos(center))) { return; } @@ -118,13 +123,11 @@ public static void addGuaranteedSpawnStart( structureManager.setStartForStructure(sectionPos, structure, new StructureStart(structure, chunk.getPos(), 0, builder.build()), chunk); } - private static BlockPos getGuaranteedSpawnIslandCenter(long seed) { - RandomSource spawnRandom = new XoroshiroRandomSource(seed); - BlockPos spawn = new BlockPos( - spawnRandom.nextIntBetweenInclusive(-200, 200), - 87, - spawnRandom.nextIntBetweenInclusive(-200, 200) - ); + public static void registerGuaranteedSpawn(long seed, BlockPos spawn) { + GUARANTEED_SPAWNS.put(seed, spawn.immutable()); + } + + public static BlockPos getGuaranteedSpawnIslandCenter(long seed, BlockPos spawn) { RandomSource random = RandomSource.create(seed ^ Mth.getSeed(spawn)); int distance = random.nextIntBetweenInclusive(SPAWN_ISLAND_MIN_DISTANCE, SPAWN_ISLAND_MAX_DISTANCE); double angle = random.nextDouble() * Math.TAU; @@ -136,6 +139,25 @@ private static BlockPos getGuaranteedSpawnIslandCenter(long seed) { ); } + private static BlockPos getGuaranteedSpawnIslandCenter(long seed) { + RandomSource spawnRandom = new XoroshiroRandomSource(seed); + return getGuaranteedSpawnIslandCenter(seed, new BlockPos( + spawnRandom.nextIntBetweenInclusive(-200, 200), + 87, + spawnRandom.nextIntBetweenInclusive(-200, 200) + )); + } + + public static BlockPos getGuaranteedSpawnIslandCenter(ServerLevel level) { + BlockPos registeredSpawn = GUARANTEED_SPAWNS.get(level.getSeed()); + + if (registeredSpawn != null) { + return getGuaranteedSpawnIslandCenter(level.getSeed(), registeredSpawn); + } + + return getGuaranteedSpawnIslandCenter(level.getSeed(), level.getSharedSpawnPos()); + } + private static ResourceLocation randomTemplate(RandomSource random) { return DIRT_ISLAND_TEMPLATES.get(random.nextInt(DIRT_ISLAND_TEMPLATES.size())); }