Tune ecosystem sensitivity and add simulation richness (Steps 1-6)
Tuning: - simulationIntervalTicks: 100→50 (faster sim feedback) - FURNACE_SCAN_INTERVAL: 100→50 (matches sim interval for pollution equilibrium) - PollutionModule.BASE_DECAY_RATE: 0.02→0.008 (slower natural decay) - SoilModule.POLLUTION_CONTAMINATION_THRESHOLD: 30→10, CONTAMINATION_FERTILITY_DRAIN: 0.002→0.005 - VegetationModule.DIEOFF_POLLUTION_THRESHOLD: 60→30 - WorldEffectsModule: lower grass-degrade/pollution-indicator thresholds so effects trigger from realistic furnace-driven pollution levels - NeoForgeWorldEffectExecutor.BLOCK_ATTEMPTS: 8→20 New features: - Campfire pollution source (0.2 air, 0.05 ground per lit campfire) - Cross-region air pollution spreading (2% of gradient per sim cycle) - SimulationManager.queueRegionForUpdate() for priority enqueueing - LivingWorldBootstrap.notifyPlayerInRegion() boosts priority when player enters region - Player region tracking in LivingWorldMod: checks every 20 MC ticks, queues update on region change - NeoForgeWorldEffectExecutor: implement SAPLING_GROWTH_BOOSTED (oak sapling placement); VEGETATION_SPREADS also places short grass and flowers; GRASS_DEGRADES_TO_DIRT clears plants and converts dirt→coarse_dirt at intensity>0.5 All 400 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,11 @@ import net.neoforged.neoforge.event.server.ServerStoppingEvent;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.CampfireBlockEntity;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -24,9 +26,14 @@ import com.livingworld.debug.LivingWorldLogger;
|
||||
import com.livingworld.platform.neoforge.NeoForgePlatformAdapter;
|
||||
import com.livingworld.platform.neoforge.NeoForgeWorldEffectExecutor;
|
||||
import com.livingworld.regions.Region;
|
||||
import com.livingworld.regions.RegionCoordinate;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Mod entrypoint for Living World.
|
||||
* <p>
|
||||
@@ -38,14 +45,20 @@ public class LivingWorldMod {
|
||||
|
||||
public static final String MOD_ID = LivingWorldConstants.MOD_ID;
|
||||
|
||||
private static final int FURNACE_SCAN_INTERVAL = 100;
|
||||
private static final double AIR_POLLUTION_PER_FURNACE = 0.5;
|
||||
private static final double GROUND_POLLUTION_PER_FURNACE = 0.1;
|
||||
private static final double WATER_POLLUTION_PER_FURNACE = 0.1;
|
||||
private static final int FURNACE_SCAN_INTERVAL = 50;
|
||||
private static final double AIR_POLLUTION_PER_FURNACE = 0.5;
|
||||
private static final double GROUND_POLLUTION_PER_FURNACE = 0.1;
|
||||
private static final double WATER_POLLUTION_PER_FURNACE = 0.1;
|
||||
private static final double AIR_POLLUTION_PER_CAMPFIRE = 0.2;
|
||||
private static final double GROUND_POLLUTION_PER_CAMPFIRE = 0.05;
|
||||
|
||||
private static final int PLAYER_CHECK_INTERVAL = 20;
|
||||
|
||||
private final LivingWorldBootstrap bootstrap;
|
||||
private MinecraftServer minecraftServer;
|
||||
private int furnaceScanTick = 0;
|
||||
private int playerCheckTick = 0;
|
||||
private final Map<UUID, RegionCoordinate> playerRegionCache = new HashMap<>();
|
||||
|
||||
public LivingWorldMod(IEventBus eventBus) {
|
||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World mod starting...");
|
||||
@@ -74,13 +87,18 @@ public class LivingWorldMod {
|
||||
NeoForge.EVENT_BUS.addListener(ServerStoppingEvent.class, event -> {
|
||||
bootstrap.onServerStopping();
|
||||
bootstrap.setOverworldRaining(null);
|
||||
playerRegionCache.clear();
|
||||
this.minecraftServer = null;
|
||||
});
|
||||
|
||||
NeoForge.EVENT_BUS.addListener(ServerTickEvent.Post.class, event -> {
|
||||
if (minecraftServer == null || !bootstrap.isServerReady()) return;
|
||||
if (++furnaceScanTick % FURNACE_SCAN_INTERVAL != 0) return;
|
||||
scanAndRecordFurnaceActivity();
|
||||
if (++furnaceScanTick % FURNACE_SCAN_INTERVAL == 0) {
|
||||
scanAndRecordFurnaceActivity();
|
||||
}
|
||||
if (++playerCheckTick % PLAYER_CHECK_INTERVAL == 0) {
|
||||
checkPlayerRegions();
|
||||
}
|
||||
});
|
||||
|
||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World Bootstrap initialized successfully.");
|
||||
@@ -92,33 +110,49 @@ public class LivingWorldMod {
|
||||
ServerLevel level = minecraftServer.getLevel(
|
||||
ResourceKey.create(Registries.DIMENSION, ResourceLocation.parse(dimensionId)));
|
||||
if (level == null) continue;
|
||||
int litCount = countLitFurnaces(level, region);
|
||||
if (litCount > 0) {
|
||||
bootstrap.handleFurnaceActivity(region,
|
||||
litCount * AIR_POLLUTION_PER_FURNACE,
|
||||
litCount * GROUND_POLLUTION_PER_FURNACE,
|
||||
litCount * WATER_POLLUTION_PER_FURNACE);
|
||||
|
||||
int baseChunkX = region.getCoordinate().x() * LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS;
|
||||
int baseChunkZ = region.getCoordinate().z() * LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS;
|
||||
|
||||
int furnaces = 0;
|
||||
int campfires = 0;
|
||||
for (int cx = baseChunkX; cx < baseChunkX + LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS; cx++) {
|
||||
for (int cz = baseChunkZ; cz < baseChunkZ + LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS; cz++) {
|
||||
LevelChunk chunk = level.getChunkSource().getChunkNow(cx, cz);
|
||||
if (chunk == null) continue;
|
||||
for (BlockEntity be : chunk.getBlockEntities().values()) {
|
||||
if (be instanceof AbstractFurnaceBlockEntity
|
||||
&& be.getBlockState().hasProperty(BlockStateProperties.LIT)
|
||||
&& Boolean.TRUE.equals(be.getBlockState().getValue(BlockStateProperties.LIT))) {
|
||||
furnaces++;
|
||||
} else if (be instanceof CampfireBlockEntity
|
||||
&& be.getBlockState().hasProperty(BlockStateProperties.LIT)
|
||||
&& Boolean.TRUE.equals(be.getBlockState().getValue(BlockStateProperties.LIT))) {
|
||||
campfires++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double air = furnaces * AIR_POLLUTION_PER_FURNACE + campfires * AIR_POLLUTION_PER_CAMPFIRE;
|
||||
double ground = furnaces * GROUND_POLLUTION_PER_FURNACE + campfires * GROUND_POLLUTION_PER_CAMPFIRE;
|
||||
double water = furnaces * WATER_POLLUTION_PER_FURNACE;
|
||||
if (air > 0 || ground > 0) {
|
||||
bootstrap.handleFurnaceActivity(region, air, ground, water);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int countLitFurnaces(ServerLevel level, Region region) {
|
||||
int count = 0;
|
||||
int baseChunkX = region.getCoordinate().x() * LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS;
|
||||
int baseChunkZ = region.getCoordinate().z() * LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS;
|
||||
for (int cx = baseChunkX; cx < baseChunkX + LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS; cx++) {
|
||||
for (int cz = baseChunkZ; cz < baseChunkZ + LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS; cz++) {
|
||||
LevelChunk chunk = level.getChunkSource().getChunkNow(cx, cz);
|
||||
if (chunk == null) continue;
|
||||
for (BlockEntity be : chunk.getBlockEntities().values()) {
|
||||
if (!(be instanceof AbstractFurnaceBlockEntity)) continue;
|
||||
if (be.getBlockState().hasProperty(BlockStateProperties.LIT)
|
||||
&& Boolean.TRUE.equals(be.getBlockState().getValue(BlockStateProperties.LIT))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
private void checkPlayerRegions() {
|
||||
for (ServerPlayer player : minecraftServer.getPlayerList().getPlayers()) {
|
||||
String dimId = player.level().dimension().location().toString();
|
||||
int regionX = (int) Math.floor(player.getX() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
|
||||
int regionZ = (int) Math.floor(player.getZ() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
|
||||
RegionCoordinate coord = new RegionCoordinate(dimId, regionX, regionZ);
|
||||
RegionCoordinate previous = playerRegionCache.put(player.getUUID(), coord);
|
||||
if (!coord.equals(previous)) {
|
||||
bootstrap.notifyPlayerInRegion(coord);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,9 @@ import com.livingworld.regions.query.RegionQueryEngine;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
/**
|
||||
@@ -219,6 +221,12 @@ public final class LivingWorldBootstrap {
|
||||
regionManager.markDirty(region);
|
||||
}
|
||||
|
||||
public void notifyPlayerInRegion(RegionCoordinate coordinate) {
|
||||
if (!serverReady || coordinate == null) return;
|
||||
regionManager.getOrCreateRegion(coordinate);
|
||||
simulationManager.queueRegionForUpdate(coordinate, 10, com.livingworld.core.simulation.UpdateReason.PLAYER_NEARBY);
|
||||
}
|
||||
|
||||
public void setOverworldRaining(BooleanSupplier supplier) {
|
||||
this.overworldRaining = supplier != null ? supplier : () -> false;
|
||||
}
|
||||
@@ -231,10 +239,49 @@ public final class LivingWorldBootstrap {
|
||||
simulationManager.onMinecraftServerTick();
|
||||
if (simulationManager.getSimulationTickCounter() != previousSimulationTick) {
|
||||
applyWeatherFeedback();
|
||||
spreadPollutionAcrossRegions();
|
||||
regionManager.saveDirtyRegions();
|
||||
}
|
||||
}
|
||||
|
||||
private static final double POLLUTION_SPREAD_RATE = 0.02;
|
||||
|
||||
private void spreadPollutionAcrossRegions() {
|
||||
Collection<Region> active = regionManager.getActiveRegions();
|
||||
if (active.size() < 2) return;
|
||||
|
||||
Map<RegionCoordinate, Region> byCoord = new HashMap<>();
|
||||
for (Region r : active) byCoord.put(r.getCoordinate(), r);
|
||||
|
||||
int[][] offsets = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
for (Region region : active) {
|
||||
RegionCoordinate coord = region.getCoordinate();
|
||||
PollutionRegionData data = region.getModuleData()
|
||||
.get(PollutionModule.MODULE_ID, PollutionRegionData.class)
|
||||
.orElse(null);
|
||||
if (data == null) continue;
|
||||
for (int[] off : offsets) {
|
||||
RegionCoordinate neighbourCoord = new RegionCoordinate(
|
||||
coord.dimensionId(), coord.x() + off[0], coord.z() + off[1]);
|
||||
Region neighbour = byCoord.get(neighbourCoord);
|
||||
if (neighbour == null) continue;
|
||||
PollutionRegionData neighbourData = neighbour.getModuleData()
|
||||
.get(PollutionModule.MODULE_ID, PollutionRegionData.class)
|
||||
.orElse(null);
|
||||
if (neighbourData == null) continue;
|
||||
double diff = data.getAirPollution() - neighbourData.getAirPollution();
|
||||
if (diff <= 0) continue;
|
||||
double transfer = diff * POLLUTION_SPREAD_RATE;
|
||||
data.addPollution(-transfer, 0, 0);
|
||||
neighbourData.addPollution(transfer, 0, 0);
|
||||
neighbour.getModuleData().put(PollutionModule.MODULE_ID, neighbourData);
|
||||
regionManager.markDirty(neighbour);
|
||||
}
|
||||
region.getModuleData().put(PollutionModule.MODULE_ID, data);
|
||||
regionManager.markDirty(region);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyWeatherFeedback() {
|
||||
boolean raining = overworldRaining.getAsBoolean();
|
||||
for (Region region : regionManager.getActiveRegions()) {
|
||||
|
||||
@@ -18,7 +18,7 @@ public final class SimulationConfig {
|
||||
private int regionSizeChunks = 8;
|
||||
|
||||
/** Interval between simulation cycles, in game ticks (must be >= 1). */
|
||||
private int simulationIntervalTicks = 100;
|
||||
private int simulationIntervalTicks = 50;
|
||||
|
||||
/** Maximum number of regions processed per cycle (must be >= 1). */
|
||||
private int maxRegionsPerCycle = 50;
|
||||
|
||||
@@ -227,6 +227,14 @@ public final class SimulationManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Enqueues a region for a priority update, e.g. because a player entered it. */
|
||||
public void queueRegionForUpdate(RegionCoordinate coordinate, int priority, UpdateReason reason) {
|
||||
if (coordinate == null) throw new IllegalArgumentException("coordinate must not be null");
|
||||
if (reason == null) throw new IllegalArgumentException("reason must not be null");
|
||||
long tick = getSimulationTickCounter();
|
||||
this.scheduler.queueRegion(new RegionUpdateJob(coordinate, priority, tick, null, reason));
|
||||
}
|
||||
|
||||
/** Returns a profile snapshot of the last completed simulation cycle. */
|
||||
public SimulationProfileSnapshot createProfileSnapshot() {
|
||||
if (profiler instanceof com.livingworld.debug.SimulationProfiler concrete) {
|
||||
|
||||
@@ -42,7 +42,7 @@ public final class PollutionModule implements SimulationModule {
|
||||
|
||||
public static final String MODULE_ID = "pollution";
|
||||
|
||||
private static final double BASE_DECAY_RATE = 0.02;
|
||||
private static final double BASE_DECAY_RATE = 0.008;
|
||||
private static final double GROUND_TO_WATER_LEACH = 0.005;
|
||||
private static final double WATER_QUALITY_IMPACT = 0.05;
|
||||
private static final double CHANGE_THRESHOLD = 0.01;
|
||||
|
||||
@@ -33,11 +33,11 @@ public final class SoilModule implements SimulationModule {
|
||||
public static final String MODULE_ID = "soil";
|
||||
|
||||
/** Pollution score above which contamination begins accumulating per tick. */
|
||||
private static final double POLLUTION_CONTAMINATION_THRESHOLD = 30.0;
|
||||
private static final double POLLUTION_CONTAMINATION_THRESHOLD = 10.0;
|
||||
/** Fraction of excess pollution score that becomes contamination per tick. */
|
||||
private static final double POLLUTION_TO_CONTAMINATION_RATE = 0.003;
|
||||
/** Fertility reduction per unit of contamination per tick. */
|
||||
private static final double CONTAMINATION_FERTILITY_DRAIN = 0.002;
|
||||
private static final double CONTAMINATION_FERTILITY_DRAIN = 0.005;
|
||||
/** Vegetation pressure threshold for fertility recovery to kick in. */
|
||||
private static final double VEGETATION_RECOVERY_THRESHOLD = 40.0;
|
||||
/** Fertility gained per unit of excess vegetation pressure per tick. */
|
||||
|
||||
@@ -52,7 +52,7 @@ public final class VegetationModule implements SimulationModule {
|
||||
|
||||
// --- die-off thresholds ---
|
||||
private static final double DIEOFF_SOIL_THRESHOLD = 20.0;
|
||||
private static final double DIEOFF_POLLUTION_THRESHOLD = 60.0;
|
||||
private static final double DIEOFF_POLLUTION_THRESHOLD = 30.0;
|
||||
private static final double GRASS_DIEOFF_RATE = 0.30;
|
||||
private static final double DEAD_ACCUMULATION_RATE = 0.20;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
|
||||
private static final int REGION_BLOCKS =
|
||||
LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16;
|
||||
private static final int BLOCK_ATTEMPTS = 8;
|
||||
private static final int BLOCK_ATTEMPTS = 20;
|
||||
private static final int MIN_GRASS_LIGHT = 9;
|
||||
|
||||
private final Supplier<MinecraftServer> serverSupplier;
|
||||
@@ -63,9 +63,9 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
spreadVegetation(level, baseX, baseZ, request.intensity());
|
||||
case POLLUTION_VISUAL_INDICATOR ->
|
||||
spawnPollutionParticles(level, baseX, baseZ, request.intensity());
|
||||
case SAPLING_GROWTH_SLOWED, SAPLING_GROWTH_BOOSTED -> {
|
||||
// Sapling growth manipulation requires mixin hooks; deferred.
|
||||
}
|
||||
case SAPLING_GROWTH_BOOSTED ->
|
||||
placeSaplings(level, baseX, baseZ, request.intensity());
|
||||
case SAPLING_GROWTH_SLOWED -> {} // requires mixin; deferred
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +74,24 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
BlockPos pos = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
|
||||
baseZ + random.nextInt(REGION_BLOCKS));
|
||||
if (pos != null && level.getBlockState(pos).is(Blocks.GRASS_BLOCK)) {
|
||||
if (pos == null) continue;
|
||||
BlockPos above = pos.above();
|
||||
// Clear plants sitting on top of the block first
|
||||
if (level.isLoaded(above)) {
|
||||
var aboveState = level.getBlockState(above);
|
||||
if (aboveState.is(Blocks.SHORT_GRASS) || aboveState.is(Blocks.TALL_GRASS)
|
||||
|| aboveState.is(Blocks.DANDELION) || aboveState.is(Blocks.POPPY)
|
||||
|| aboveState.is(Blocks.OAK_SAPLING)) {
|
||||
level.setBlock(above, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
var state = level.getBlockState(pos);
|
||||
if (state.is(Blocks.GRASS_BLOCK)) {
|
||||
level.setBlock(pos, Blocks.DIRT.defaultBlockState(), Block.UPDATE_ALL);
|
||||
LivingWorldLogger.info(DiagnosticCategory.SIMULATION,
|
||||
"WorldEffect GRASS_DEGRADES_TO_DIRT at " + pos);
|
||||
} else if (state.is(Blocks.DIRT) && intensity > 0.5) {
|
||||
level.setBlock(pos, Blocks.COARSE_DIRT.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,22 +101,45 @@ public final class NeoForgeWorldEffectExecutor implements WorldEffectConsumer {
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
BlockPos pos = surfaceAt(level, baseX + random.nextInt(REGION_BLOCKS),
|
||||
baseZ + random.nextInt(REGION_BLOCKS));
|
||||
if (pos == null || !level.getBlockState(pos).is(Blocks.DIRT)) {
|
||||
continue;
|
||||
}
|
||||
if (pos == null) continue;
|
||||
BlockPos above = pos.above();
|
||||
if (level.getBlockState(above).isAir()
|
||||
&& level.getRawBrightness(above, 0) >= MIN_GRASS_LIGHT) {
|
||||
boolean aboveAir = level.isLoaded(above) && level.getBlockState(above).isAir();
|
||||
boolean brightEnough = aboveAir && level.getRawBrightness(above, 0) >= MIN_GRASS_LIGHT;
|
||||
|
||||
var state = level.getBlockState(pos);
|
||||
if (state.is(Blocks.DIRT) && brightEnough) {
|
||||
level.setBlock(pos, Blocks.GRASS_BLOCK.defaultBlockState(), Block.UPDATE_ALL);
|
||||
LivingWorldLogger.info(DiagnosticCategory.SIMULATION,
|
||||
"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
|
||||
? (random.nextBoolean() ? Blocks.DANDELION : Blocks.POPPY)
|
||||
: Blocks.SHORT_GRASS;
|
||||
level.setBlock(above, plant.defaultBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void placeSaplings(ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
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),
|
||||
baseZ + random.nextInt(REGION_BLOCKS));
|
||||
if (pos == null) continue;
|
||||
var state = level.getBlockState(pos);
|
||||
if (!state.is(Blocks.GRASS_BLOCK) && !state.is(Blocks.DIRT)) continue;
|
||||
BlockPos above = pos.above();
|
||||
if (!level.isLoaded(above) || !level.getBlockState(above).isAir()) continue;
|
||||
level.setBlock(above, Blocks.OAK_SAPLING.defaultBlockState(), Block.UPDATE_ALL);
|
||||
LivingWorldLogger.info(DiagnosticCategory.SIMULATION,
|
||||
"WorldEffect SAPLING_GROWTH_BOOSTED at " + pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnPollutionParticles(
|
||||
ServerLevel level, int baseX, int baseZ, double intensity) {
|
||||
int count = Math.max(1, (int) (intensity * 5));
|
||||
int count = Math.max(1, (int) (intensity * 8));
|
||||
for (int i = 0; i < count; i++) {
|
||||
double x = baseX + random.nextDouble() * REGION_BLOCKS;
|
||||
double z = baseZ + random.nextDouble() * REGION_BLOCKS;
|
||||
|
||||
@@ -41,7 +41,7 @@ class LivingWorldBootstrapTest {
|
||||
assertTrue(bootstrap.getServices().isRegistered(CoreServices.TIME));
|
||||
assertTrue(bootstrap.getServices().isRegistered(CoreServices.DEBUG));
|
||||
|
||||
for (int tick = 0; tick < 100; tick++) {
|
||||
for (int tick = 0; tick < 50; tick++) {
|
||||
bootstrap.onServerTick();
|
||||
}
|
||||
TimeService timeService = bootstrap.getServices().get(CoreServices.TIME);
|
||||
|
||||
@@ -31,7 +31,7 @@ class SimulationConfigTest {
|
||||
@Test
|
||||
void defaultSimulationIntervalTicks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertEquals(100, config.getSimulationIntervalTicks());
|
||||
assertEquals(50, config.getSimulationIntervalTicks());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -276,6 +276,6 @@ class SimulationConfigTest {
|
||||
final String result = config.toString();
|
||||
assertTrue(result.contains("SimulationConfig"));
|
||||
assertTrue(result.contains("regionSizeChunks=8"));
|
||||
assertTrue(result.contains("simulationIntervalTicks=100"));
|
||||
assertTrue(result.contains("simulationIntervalTicks=50"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user