Complete phase 5 ecology systems
This commit is contained in:
@@ -7,6 +7,8 @@ import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.entity.living.FinalizeSpawnEvent;
|
||||
import net.neoforged.neoforge.event.entity.living.LivingDeathEvent;
|
||||
import net.neoforged.neoforge.event.entity.EntityLeaveLevelEvent;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.neoforged.neoforge.event.level.BlockEvent;
|
||||
import net.neoforged.neoforge.event.level.ChunkEvent;
|
||||
@@ -30,7 +32,9 @@ import net.minecraft.world.entity.animal.Animal;
|
||||
import net.minecraft.world.entity.animal.AbstractFish;
|
||||
import net.minecraft.world.entity.animal.Dolphin;
|
||||
import net.minecraft.world.entity.animal.Squid;
|
||||
import net.minecraft.world.entity.animal.Bee;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import net.minecraft.world.entity.MobSpawnType;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
@@ -124,6 +128,8 @@ public class LivingWorldMod {
|
||||
/** Stores playerCheckTick value when a region was last water-body-scanned. */
|
||||
private final Map<RegionCoordinate, Integer> waterBodyLastScan = new HashMap<>();
|
||||
private final Set<RegionCoordinate> elevationInitialized = new HashSet<>();
|
||||
private final Map<UUID, RegionCoordinate> trackedPassiveMobs = new HashMap<>();
|
||||
private boolean migrationCompanionSpawn;
|
||||
/** Stores playerCheckTick when a region last played an ambient sound (per-region throttle). */
|
||||
private final Map<RegionCoordinate, Integer> regionSoundLastTick = new HashMap<>();
|
||||
|
||||
@@ -153,7 +159,9 @@ public class LivingWorldMod {
|
||||
this.minecraftServer = event.getServer();
|
||||
bootstrap.onServerStarted();
|
||||
bootstrap.getWorldEffectsModule().registerConsumer(
|
||||
new NeoForgeWorldEffectExecutor(() -> minecraftServer));
|
||||
new NeoForgeWorldEffectExecutor(
|
||||
() -> minecraftServer,
|
||||
coord -> !bootstrap.isPollinatorCollapse(coord)));
|
||||
bootstrap.setOverworldRaining(
|
||||
() -> minecraftServer != null && minecraftServer.overworld().isRaining());
|
||||
bootstrap.setAbsoluteDaySupplier(
|
||||
@@ -240,6 +248,7 @@ public class LivingWorldMod {
|
||||
waterBodyLastScan.clear();
|
||||
elevationInitialized.clear();
|
||||
regionSoundLastTick.clear();
|
||||
trackedPassiveMobs.clear();
|
||||
tideTick = 0;
|
||||
lastTideLevel = 0.0;
|
||||
riverCurrentTick = 0;
|
||||
@@ -298,19 +307,67 @@ public class LivingWorldMod {
|
||||
event.setSpawnCancelled(true);
|
||||
return;
|
||||
}
|
||||
if (event.getEntity() instanceof Bee && bootstrap.isPollinatorCollapse(spawnCoord)) {
|
||||
event.setSpawnCancelled(true);
|
||||
return;
|
||||
}
|
||||
if (event.getEntity() instanceof Animal) {
|
||||
if (health < PASSIVE_SUPPRESS_HEALTH) {
|
||||
if (migrationCompanionSpawn) {
|
||||
trackedPassiveMobs.put(event.getEntity().getUUID(), spawnCoord);
|
||||
bootstrap.recordPassiveMobSpawn(spawnCoord);
|
||||
return;
|
||||
}
|
||||
if (bootstrap.isLocallyExtinct(spawnCoord)) {
|
||||
event.setSpawnCancelled(true);
|
||||
return;
|
||||
}
|
||||
double attraction = bootstrap.getMigrationAttraction(spawnCoord);
|
||||
if (health < PASSIVE_SUPPRESS_HEALTH || attraction < 0.0) {
|
||||
double chance = (PASSIVE_SUPPRESS_HEALTH - health) / PASSIVE_SUPPRESS_HEALTH * 0.7;
|
||||
if (random.nextDouble() < chance) event.setSpawnCancelled(true);
|
||||
chance = Math.max(chance, -attraction * 0.5);
|
||||
if (random.nextDouble() < chance) {
|
||||
event.setSpawnCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
trackedPassiveMobs.put(event.getEntity().getUUID(), spawnCoord);
|
||||
bootstrap.recordPassiveMobSpawn(spawnCoord);
|
||||
if (attraction >= 0.5 && bootstrap.getPassiveMobPressure(spawnCoord) < 12
|
||||
&& random.nextDouble() < 0.50) {
|
||||
migrationCompanionSpawn = true;
|
||||
try {
|
||||
event.getEntity().getType().spawn(
|
||||
level,
|
||||
event.getEntity().blockPosition().offset(
|
||||
random.nextInt(5) - 2, 0, random.nextInt(5) - 2),
|
||||
MobSpawnType.NATURAL);
|
||||
} finally {
|
||||
migrationCompanionSpawn = false;
|
||||
}
|
||||
}
|
||||
} else if (event.getEntity() instanceof Monster) {
|
||||
int preyPressure = bootstrap.getPassiveMobPressure(spawnCoord);
|
||||
if (preyPressure == 0 && random.nextDouble() < 0.55) {
|
||||
event.setSpawnCancelled(true);
|
||||
return;
|
||||
}
|
||||
if (health > HOSTILE_SUPPRESS_HEALTH) {
|
||||
double chance = (health - HOSTILE_SUPPRESS_HEALTH) / (100.0 - HOSTILE_SUPPRESS_HEALTH) * 0.5;
|
||||
if (preyPressure > 8) chance *= 0.5;
|
||||
if (random.nextDouble() < chance) event.setSpawnCancelled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
NeoForge.EVENT_BUS.addListener(LivingDeathEvent.class, event -> {
|
||||
RegionCoordinate coord = trackedPassiveMobs.remove(event.getEntity().getUUID());
|
||||
if (coord != null) bootstrap.recordPassiveMobDeparture(coord);
|
||||
});
|
||||
NeoForge.EVENT_BUS.addListener(EntityLeaveLevelEvent.class, event -> {
|
||||
RegionCoordinate coord = trackedPassiveMobs.remove(event.getEntity().getUUID());
|
||||
if (coord != null) bootstrap.recordPassiveMobDeparture(coord);
|
||||
});
|
||||
|
||||
// Step 4: Agriculture — bone meal boosts soil fertility.
|
||||
NeoForge.EVENT_BUS.addListener(PlayerInteractEvent.RightClickBlock.class, event -> {
|
||||
if (!bootstrap.isServerReady()) return;
|
||||
@@ -339,6 +396,15 @@ public class LivingWorldMod {
|
||||
BlockPos pos = event.getPos();
|
||||
bootstrap.handleCropHarvest(level.dimension().location().toString(), pos.getX(), pos.getZ());
|
||||
});
|
||||
NeoForge.EVENT_BUS.addListener(BlockEvent.EntityPlaceEvent.class, event -> {
|
||||
if (!bootstrap.isServerReady() || !(event.getLevel() instanceof ServerLevel level)) return;
|
||||
if (!event.getPlacedBlock().is(BlockTags.SAPLINGS)) return;
|
||||
RegionCoordinate coord = RegionCoordinate.fromBlock(
|
||||
level.dimension().location().toString(),
|
||||
event.getPos().getX(), event.getPos().getZ(),
|
||||
LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS);
|
||||
bootstrap.handleSaplingPlaced(coord, level.getGameTime() / 24000L);
|
||||
});
|
||||
|
||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World Bootstrap initialized successfully.");
|
||||
}
|
||||
|
||||
@@ -145,6 +145,15 @@ public final class LivingWorldBootstrap {
|
||||
private final Map<RegionCoordinate, Integer> blizzardHistory = new HashMap<>();
|
||||
private int hydrologyCycleTick = 0;
|
||||
private int dynamicSeaLevel = 62;
|
||||
private final Map<RegionCoordinate, Integer> passiveMobPressure = new HashMap<>();
|
||||
private final Map<RegionCoordinate, Integer> wildlifeAbsentCycles = new HashMap<>();
|
||||
private final Set<RegionCoordinate> extinctRegions = new HashSet<>();
|
||||
private final Set<RegionCoordinate> pollinatorCollapseRegions = new HashSet<>();
|
||||
private final Map<RegionCoordinate, Long> saplingPlacementDay = new HashMap<>();
|
||||
private final Map<RegionCoordinate, Integer> saplingPlacementCount = new HashMap<>();
|
||||
private final Map<RegionCoordinate, Integer> rewildingBoostCycles = new HashMap<>();
|
||||
private final Map<RegionCoordinate, Integer> bogWetCycles = new HashMap<>();
|
||||
private final Set<RegionCoordinate> waterloggedRegions = new HashSet<>();
|
||||
|
||||
private PlatformAdapter platformAdapter;
|
||||
private Path worldSaveDirectory;
|
||||
@@ -322,6 +331,15 @@ public final class LivingWorldBootstrap {
|
||||
blizzardHistory.clear();
|
||||
hydrologyCycleTick = 0;
|
||||
dynamicSeaLevel = ecosystemTuning.getSeaLevel();
|
||||
passiveMobPressure.clear();
|
||||
wildlifeAbsentCycles.clear();
|
||||
extinctRegions.clear();
|
||||
pollinatorCollapseRegions.clear();
|
||||
saplingPlacementDay.clear();
|
||||
saplingPlacementCount.clear();
|
||||
rewildingBoostCycles.clear();
|
||||
bogWetCycles.clear();
|
||||
waterloggedRegions.clear();
|
||||
simSpeedMultiplier = 1;
|
||||
serverReady = false;
|
||||
LivingWorldLogger.info(
|
||||
@@ -387,7 +405,8 @@ public final class LivingWorldBootstrap {
|
||||
.get(SoilModule.MODULE_ID, SoilRegionData.class)
|
||||
.orElse(null);
|
||||
if (soil == null) return;
|
||||
soil.setFertility(Math.min(100, soil.getFertility() + 2.0));
|
||||
double gain = isPollinatorCollapse(coord) ? 1.0 : 2.0;
|
||||
soil.setFertility(Math.min(100, soil.getFertility() + gain));
|
||||
region.getModuleData().put(SoilModule.MODULE_ID, soil);
|
||||
regionManager.markDirty(region);
|
||||
});
|
||||
@@ -488,6 +507,136 @@ public final class LivingWorldBootstrap {
|
||||
applyGeologicalActivity();
|
||||
}
|
||||
applyHydrologyExpansion();
|
||||
applyEcologyExpansion();
|
||||
}
|
||||
|
||||
public void recordPassiveMobSpawn(RegionCoordinate coord) {
|
||||
if (coord != null) passiveMobPressure.merge(coord, 1, Integer::sum);
|
||||
}
|
||||
|
||||
public void recordPassiveMobDeparture(RegionCoordinate coord) {
|
||||
if (coord != null) {
|
||||
passiveMobPressure.compute(coord,
|
||||
(ignored, pressure) -> Math.max(0, (pressure == null ? 0 : pressure) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public int getPassiveMobPressure(RegionCoordinate coord) {
|
||||
return passiveMobPressure.getOrDefault(coord, 0);
|
||||
}
|
||||
|
||||
public double getMigrationAttraction(RegionCoordinate coord) {
|
||||
if (coord == null || regionManager == null) return 0.0;
|
||||
Region region = regionManager.resolve(coord).orElse(null);
|
||||
if (region == null) return 0.0;
|
||||
double ownHealth = region.getMetrics().getEcosystemHealth();
|
||||
double bestNeighbour = ownHealth;
|
||||
int[][] offsets = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
for (int[] offset : offsets) {
|
||||
RegionCoordinate neighbour = new RegionCoordinate(
|
||||
coord.dimensionId(), coord.x() + offset[0], coord.z() + offset[1]);
|
||||
Region adjacent = regionManager.resolve(neighbour).orElse(null);
|
||||
if (adjacent != null) {
|
||||
bestNeighbour = Math.max(bestNeighbour, adjacent.getMetrics().getEcosystemHealth());
|
||||
}
|
||||
}
|
||||
return Math.max(-1.0, Math.min(1.0, (ownHealth - bestNeighbour + 25.0) / 50.0));
|
||||
}
|
||||
|
||||
public boolean isLocallyExtinct(RegionCoordinate coord) {
|
||||
return coord != null && extinctRegions.contains(coord);
|
||||
}
|
||||
|
||||
public boolean isPollinatorCollapse(RegionCoordinate coord) {
|
||||
return coord != null && pollinatorCollapseRegions.contains(coord);
|
||||
}
|
||||
|
||||
public void handleSaplingPlaced(RegionCoordinate coord, long absoluteDay) {
|
||||
if (!serverReady || coord == null) return;
|
||||
long previousDay = saplingPlacementDay.getOrDefault(coord, Long.MIN_VALUE);
|
||||
if (previousDay != absoluteDay) {
|
||||
saplingPlacementDay.put(coord, absoluteDay);
|
||||
saplingPlacementCount.put(coord, 0);
|
||||
}
|
||||
int count = saplingPlacementCount.merge(coord, 1, Integer::sum);
|
||||
if (count < 10 || rewildingBoostCycles.containsKey(coord)) return;
|
||||
Region region = regionManager.resolve(coord).orElse(null);
|
||||
if (region == null) return;
|
||||
RecoveryRegionData recovery = region.getModuleData()
|
||||
.get(RecoveryModule.MODULE_ID, RecoveryRegionData.class).orElse(null);
|
||||
if (recovery == null || recovery.getSuccessionStage().ordinal()
|
||||
> SuccessionStage.SPARSE_GRASS.ordinal()) return;
|
||||
rewildingBoostCycles.put(coord, 20);
|
||||
pendingEventMessages.add("[LW] Rewilding effort detected in region ("
|
||||
+ coord.x() + "," + coord.z() + ") - ecosystem recovering!");
|
||||
}
|
||||
|
||||
private void applyEcologyExpansion() {
|
||||
for (Region region : regionManager.getActiveRegions()) {
|
||||
RegionCoordinate coord = region.getCoordinate();
|
||||
RecoveryRegionData recovery = region.getModuleData()
|
||||
.get(RecoveryModule.MODULE_ID, RecoveryRegionData.class).orElse(null);
|
||||
PollutionRegionData pollution = region.getModuleData()
|
||||
.get(PollutionModule.MODULE_ID, PollutionRegionData.class).orElse(null);
|
||||
VegetationRegionData vegetation = region.getModuleData()
|
||||
.get(VegetationModule.MODULE_ID, VegetationRegionData.class).orElse(null);
|
||||
WaterRegionData water = region.getModuleData()
|
||||
.get(WaterModule.MODULE_ID, WaterRegionData.class).orElse(null);
|
||||
if (recovery == null || pollution == null || vegetation == null || water == null) continue;
|
||||
|
||||
int pressure = passiveMobPressure.getOrDefault(coord, 0);
|
||||
if (pressure == 0) {
|
||||
int absent = wildlifeAbsentCycles.merge(coord, 1, Integer::sum);
|
||||
if (absent >= 100) extinctRegions.add(coord);
|
||||
} else {
|
||||
wildlifeAbsentCycles.remove(coord);
|
||||
}
|
||||
if (extinctRegions.contains(coord)
|
||||
&& recovery.getSuccessionStage().ordinal()
|
||||
>= SuccessionStage.YOUNG_WOODLAND.ordinal()) {
|
||||
extinctRegions.remove(coord);
|
||||
wildlifeAbsentCycles.remove(coord);
|
||||
pendingEventMessages.add("[LW] Wildlife returning to region ("
|
||||
+ coord.x() + "," + coord.z() + ").");
|
||||
}
|
||||
|
||||
if (pollution.getGroundPollution() > 50.0
|
||||
&& vegetation.getFlowerPressure() > 5.0) {
|
||||
pollinatorCollapseRegions.add(coord);
|
||||
} else if (pollution.getGroundPollution() < 20.0) {
|
||||
pollinatorCollapseRegions.remove(coord);
|
||||
}
|
||||
|
||||
Integer boost = rewildingBoostCycles.get(coord);
|
||||
if (boost != null) {
|
||||
if (boost == 20 || boost == 10) recovery.boostOneStage();
|
||||
pollution.addPollution(-1.5, -1.5, -1.5);
|
||||
if (boost <= 1) rewildingBoostCycles.remove(coord);
|
||||
else rewildingBoostCycles.put(coord, boost - 1);
|
||||
}
|
||||
|
||||
boolean bogCandidate = coastalWetlandRegions.contains(coord)
|
||||
&& waterloggedRegions.contains(coord)
|
||||
&& water.getWaterAvailability() > 75.0
|
||||
&& recovery.getSuccessionStage().ordinal() <= SuccessionStage.GRASSLAND.ordinal();
|
||||
if (bogCandidate) {
|
||||
int wetCycles = bogWetCycles.merge(coord, 1, Integer::sum);
|
||||
if (wetCycles >= 30) {
|
||||
recovery.setMaxSuccessionStage(SuccessionStage.SCRUBLAND);
|
||||
if (worldEffectsModule != null) {
|
||||
worldEffectsModule.queueEffect(new WorldEffectRequest(
|
||||
WorldEffectType.PEAT_FORMS, coord,
|
||||
Math.min(1.0, wetCycles / 100.0)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bogWetCycles.remove(coord);
|
||||
}
|
||||
|
||||
region.getModuleData().put(RecoveryModule.MODULE_ID, recovery);
|
||||
region.getModuleData().put(PollutionModule.MODULE_ID, pollution);
|
||||
regionManager.markDirty(region);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyHydrologyExpansion() {
|
||||
@@ -2080,6 +2229,7 @@ public final class LivingWorldBootstrap {
|
||||
/** Applies a water quality and purification bonus to regions adjacent to or containing water bodies. */
|
||||
public void applyWaterBodyBoost(RegionCoordinate coord) {
|
||||
if (!serverReady || coord == null) return;
|
||||
waterloggedRegions.add(coord);
|
||||
regionManager.resolve(coord).ifPresent(region -> {
|
||||
WaterRegionData water = region.getModuleData()
|
||||
.get(WaterModule.MODULE_ID, WaterRegionData.class)
|
||||
|
||||
@@ -72,6 +72,16 @@ public final class RecoveryRegionData {
|
||||
}
|
||||
}
|
||||
|
||||
/** Advances one restoration stage without bypassing the region's biome cap. */
|
||||
public void boostOneStage() {
|
||||
if (successionStage.hasNext()
|
||||
&& successionStage.next().ordinal() <= maxSuccessionStage.ordinal()) {
|
||||
successionStage = successionStage.next();
|
||||
recoveryProgress = 0.0;
|
||||
damageAccumulation = Math.max(0.0, damageAccumulation - 20.0);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Mutation
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@@ -187,4 +187,7 @@ public enum WorldEffectType {
|
||||
|
||||
/** Falling river water deepens its impact basin. */
|
||||
PLUNGE_POOL_DEEPENS,
|
||||
|
||||
/** Waterlogged wetland soil slowly develops a persistent peat profile. */
|
||||
PEAT_FORMS,
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
@@ -42,12 +43,20 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
|
||||
private final Supplier<MinecraftServer> serverSupplier;
|
||||
private final Random random = new Random();
|
||||
private final Predicate<com.livingworld.regions.RegionCoordinate> flowersAllowed;
|
||||
private record TimedWater(BlockPos pos, long expiresAt) {}
|
||||
private final Map<ResourceKey<Level>, List<TimedWater>> geyserWater = new HashMap<>();
|
||||
private final Map<ResourceKey<Level>, List<TimedWater>> floodWater = new HashMap<>();
|
||||
|
||||
public NeoForgeWorldEffectExecutor(Supplier<MinecraftServer> serverSupplier) {
|
||||
this(serverSupplier, ignored -> true);
|
||||
}
|
||||
|
||||
public NeoForgeWorldEffectExecutor(
|
||||
Supplier<MinecraftServer> serverSupplier,
|
||||
Predicate<com.livingworld.regions.RegionCoordinate> flowersAllowed) {
|
||||
this.serverSupplier = serverSupplier;
|
||||
this.flowersAllowed = flowersAllowed != null ? flowersAllowed : ignored -> true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,7 +81,8 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
case GRASS_DEGRADES_TO_DIRT ->
|
||||
degradeGrass(level, baseX, baseZ, request.intensity());
|
||||
case VEGETATION_SPREADS ->
|
||||
spreadVegetation(level, baseX, baseZ, request.intensity());
|
||||
spreadVegetation(level, baseX, baseZ, request.intensity(),
|
||||
flowersAllowed.test(request.region()));
|
||||
case POLLUTION_VISUAL_INDICATOR ->
|
||||
spawnPollutionParticles(level, baseX, baseZ, request.intensity());
|
||||
case SAPLING_GROWTH_BOOSTED ->
|
||||
@@ -150,6 +160,8 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
polishGlacierRock(level, baseX, baseZ, request.intensity());
|
||||
case PLUNGE_POOL_DEEPENS ->
|
||||
deepenPlungePool(level, baseX, baseZ, request.intensity());
|
||||
case PEAT_FORMS ->
|
||||
formPeat(level, baseX, baseZ, request.intensity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +192,8 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
}
|
||||
}
|
||||
|
||||
private void spreadVegetation(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
private void spreadVegetation(
|
||||
ServerLevel level, int baseX, int baseZ, double intensity, boolean allowFlowers) {
|
||||
int attempts = Math.max(1, (int) (intensity * BLOCK_ATTEMPTS));
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
BlockPos pos = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
|
||||
@@ -197,7 +210,7 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
"WorldEffect VEGETATION_SPREADS at " + pos);
|
||||
} else if (state.is(Blocks.GRASS_BLOCK) && brightEnough) {
|
||||
// Add surface plants — flowers 1-in-5 chance, short grass otherwise
|
||||
Block plant = random.nextInt(5) == 0
|
||||
Block plant = allowFlowers && random.nextInt(5) == 0
|
||||
? (random.nextBoolean() ? Blocks.DANDELION : Blocks.POPPY)
|
||||
: Blocks.SHORT_GRASS;
|
||||
level.setBlock(above, plant.defaultBlockState(), Block.UPDATE_ALL);
|
||||
@@ -1147,6 +1160,23 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
}
|
||||
}
|
||||
|
||||
private void formPeat(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
if (random.nextDouble() > 0.20) return;
|
||||
for (int i = 0; i < Math.max(2, (int) (intensity * 8)); i++) {
|
||||
BlockPos pos = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
|
||||
baseZ + random.nextInt(REGION_BLOCKS));
|
||||
if (pos == null) continue;
|
||||
var state = level.getBlockState(pos);
|
||||
if (state.is(Blocks.GRASS_BLOCK) || state.is(Blocks.DIRT)) {
|
||||
level.setBlock(pos, Blocks.MUD.defaultBlockState(), Block.UPDATE_ALL);
|
||||
} else if (state.is(Blocks.MUD)) {
|
||||
level.setBlock(pos, Blocks.PACKED_MUD.defaultBlockState(), Block.UPDATE_ALL);
|
||||
} else if (state.is(Blocks.PACKED_MUD) && intensity > 0.7) {
|
||||
level.setBlock(pos, Blocks.BROWN_TERRACOTTA.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BlockPos surfaceAt(ServerLevel level, int x, int z) {
|
||||
int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
|
||||
if (y < level.getMinBuildHeight()) {
|
||||
|
||||
Reference in New Issue
Block a user