From dfa84a934735f11332cbaea595d8fc3bc2aeb89d Mon Sep 17 00:00:00 2001 From: George Date: Sun, 7 Jun 2026 16:57:11 +0100 Subject: [PATCH] Wire furnace burning to air pollution input PollutionModule only decayed pollution; no source ever fed it. Every 100 server ticks (one simulation interval), LivingWorldMod now scans each active region's loaded chunks for lit AbstractFurnaceBlockEntity instances (covers standard furnace, blast furnace, smoker) using getChunkNow so unloaded chunks are skipped cheaply. Per lit furnace per scan: +0.5 air, +0.1 ground pollution. With 4 furnaces this drives air pollution to an equilibrium of ~55 (decay rate 3.6%/tick), giving a clearly visible pollution score in /lw region info. Bootstrap gains getActiveRegions() and handleFurnaceActivity() for the platform layer to call without needing Minecraft imports. Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/livingworld/LivingWorldMod.java | 57 +++++++++++++++++++ .../bootstrap/LivingWorldBootstrap.java | 22 +++++++ 2 files changed, 79 insertions(+) diff --git a/src/main/java/com/livingworld/LivingWorldMod.java b/src/main/java/com/livingworld/LivingWorldMod.java index daf1e4a..a72c31e 100644 --- a/src/main/java/com/livingworld/LivingWorldMod.java +++ b/src/main/java/com/livingworld/LivingWorldMod.java @@ -8,6 +8,14 @@ import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStartingEvent; 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.state.properties.BlockStateProperties; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.core.registries.Registries; import com.livingworld.bootstrap.LivingWorldBootstrap; import com.livingworld.core.LivingWorldConstants; @@ -15,7 +23,9 @@ import com.livingworld.debug.DiagnosticCategory; import com.livingworld.debug.LivingWorldLogger; import com.livingworld.platform.neoforge.NeoForgePlatformAdapter; import com.livingworld.platform.neoforge.NeoForgeWorldEffectExecutor; +import com.livingworld.regions.Region; import net.minecraft.server.MinecraftServer; +import net.neoforged.neoforge.event.tick.ServerTickEvent; /** * Mod entrypoint for Living World. @@ -27,8 +37,14 @@ import net.minecraft.server.MinecraftServer; 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 SOIL_POLLUTION_PER_FURNACE = 0.1; + private final LivingWorldBootstrap bootstrap; private MinecraftServer minecraftServer; + private int furnaceScanTick = 0; public LivingWorldMod(IEventBus eventBus) { LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World mod starting..."); @@ -60,6 +76,47 @@ public class LivingWorldMod { 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(); + }); + LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World Bootstrap initialized successfully."); } + + private void scanAndRecordFurnaceActivity() { + for (Region region : bootstrap.getActiveRegions()) { + String dimensionId = region.getCoordinate().dimensionId(); + 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 * SOIL_POLLUTION_PER_FURNACE); + } + } + } + + 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++; + } + } + } + } + return count; + } } diff --git a/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java b/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java index 31fb6ee..e3d873a 100644 --- a/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java +++ b/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java @@ -46,6 +46,8 @@ import com.livingworld.regions.cache.RegionCache; import com.livingworld.regions.query.RegionQueryEngine; import com.mojang.brigadier.CommandDispatcher; import java.nio.file.Path; +import java.util.Collection; +import java.util.List; import net.minecraft.commands.CommandSourceStack; /** @@ -195,6 +197,26 @@ public final class LivingWorldBootstrap { * Sets the supplier that reports whether it is currently raining in the overworld. * Called from the platform layer after the server starts and cleared on stop. */ + /** Returns all currently cached (active) regions. Safe to call from the platform layer. */ + public Collection getActiveRegions() { + if (!serverReady) return List.of(); + return regionManager.getActiveRegions(); + } + + /** + * Records pollution produced by burning activity (e.g. furnaces) in a region. + * Called from the platform layer once per simulation interval. + */ + public void handleFurnaceActivity(Region region, double airAmount, double groundAmount) { + if (!serverReady || region == null) return; + PollutionRegionData data = region.getModuleData() + .get(PollutionModule.MODULE_ID, PollutionRegionData.class) + .orElseGet(PollutionRegionData::defaults); + data.addPollution(airAmount, groundAmount, 0.0); + region.getModuleData().put(PollutionModule.MODULE_ID, data); + regionManager.markDirty(region); + } + public void setOverworldRaining(BooleanSupplier supplier) { this.overworldRaining = supplier != null ? supplier : () -> false; }