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; }