diff --git a/src/main/java/com/livingworld/LivingWorldMod.java b/src/main/java/com/livingworld/LivingWorldMod.java index 91ce5d2..fc843ca 100644 --- a/src/main/java/com/livingworld/LivingWorldMod.java +++ b/src/main/java/com/livingworld/LivingWorldMod.java @@ -36,6 +36,7 @@ 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.entity.item.ItemEntity; import net.minecraft.world.item.Items; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biomes; @@ -616,6 +617,10 @@ public class LivingWorldMod { Math.max(movement.y, 0.12), movement.z + (random.nextDouble() - 0.5) * 0.35); } + if (bootstrap.isStormIncoming(coord) && random.nextInt(8) == 0) { + playAmbientSound(player, Holder.direct(SoundEvents.WEATHER_RAIN), + 0.08f, 0.75f); + } } } } @@ -651,6 +656,7 @@ public class LivingWorldMod { double tidalPushZ = Math.sin(windAngle) * tidalCurrentMag * TIDAL_CURRENT_STRENGTH; boolean hasTidalCurrent = Math.abs(tidalCurrentMag) > 0.1; + Set processedItems = new HashSet<>(); for (ServerPlayer player : minecraftServer.getPlayerList().getPlayers()) { if (!(player.level() instanceof ServerLevel level)) continue; BlockPos pos = player.blockPosition(); @@ -694,6 +700,31 @@ public class LivingWorldMod { var mm = mob.getDeltaMovement(); mob.setDeltaMovement(mm.x + mobFlow.x * mobForce, mm.y, mm.z + mobFlow.z * mobForce); } + + var nearbyItems = entityLevel.getEntitiesOfClass( + ItemEntity.class, player.getBoundingBox().inflate(48)); + for (ItemEntity item : nearbyItems) { + if (!processedItems.add(item.getUUID())) continue; + var itemFluid = entityLevel.getFluidState(item.blockPosition()); + if (!itemFluid.is(FluidTags.WATER)) continue; + var movement = item.getDeltaMovement(); + if (!itemFluid.isSource()) { + var itemFlow = itemFluid.getFlow(entityLevel, item.blockPosition()); + double force = CURRENT_STRENGTH * (itemFluid.getAmount() / 8.0) * 0.8; + item.setDeltaMovement(movement.x + itemFlow.x * force, + movement.y, movement.z + itemFlow.z * force); + } else if (hasTidalCurrent) { + RegionCoordinate itemCoord = RegionCoordinate.fromBlock( + entityLevel.dimension().location().toString(), + item.blockPosition().getX(), item.blockPosition().getZ(), + LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS); + Double elevation = bootstrap.getRegionElevation(itemCoord); + if (elevation != null && elevation <= seaLevel + 6) { + item.setDeltaMovement(movement.x + tidalPushX, + movement.y, movement.z + tidalPushZ); + } + } + } } } @@ -710,14 +741,16 @@ public class LivingWorldMod { RegionCoordinate coord = new RegionCoordinate(dimId, regionX, regionZ); // Volcanic ambience — highest priority when eruption is active - String volcanoPhase = bootstrap.getVolcanoPhaseAt(coord); + String volcanoPhase = bootstrap.getNearbyVolcanoPhase(coord, 3); if (volcanoPhase != null) { - if ("ERUPTING".equals(volcanoPhase) && random.nextInt(4) == 0) { - playAmbientSound(player, Holder.direct(SoundEvents.FIRE_AMBIENT), 0.55f, 0.8f + random.nextFloat() * 0.4f); + if ("ERUPTING".equals(volcanoPhase) && random.nextInt(3) == 0) { + playAmbientSound(player, SoundEvents.AMBIENT_BASALT_DELTAS_MOOD, + 0.45f, 0.55f + random.nextFloat() * 0.25f); return true; } - if ("BUILDING".equals(volcanoPhase) && random.nextInt(20) == 0) { - playAmbientSound(player, SoundEvents.AMBIENT_BASALT_DELTAS_MOOD, 0.25f, 0.7f + random.nextFloat() * 0.2f); + if ("BUILDING".equals(volcanoPhase) && random.nextInt(10) == 0) { + playAmbientSound(player, SoundEvents.AMBIENT_BASALT_DELTAS_MOOD, + 0.22f, 0.75f + random.nextFloat() * 0.15f); return true; } } diff --git a/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java b/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java index 20f07b7..5173eb2 100644 --- a/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java +++ b/src/main/java/com/livingworld/bootstrap/LivingWorldBootstrap.java @@ -1489,6 +1489,33 @@ public final class LivingWorldBootstrap { return phase != null ? phase.name() : null; } + /** Returns the strongest volcanic phase within the requested region radius. */ + public String getNearbyVolcanoPhase(RegionCoordinate coord, int radius) { + if (coord == null) return null; + VolcanoPhase strongest = null; + for (RegionCoordinate volcanic : volcanicRegions) { + if (!volcanic.dimensionId().equals(coord.dimensionId())) continue; + if (Math.abs(volcanic.x() - coord.x()) > radius + || Math.abs(volcanic.z() - coord.z()) > radius) continue; + VolcanoPhase phase = volcanoPhase.get(volcanic); + if (phase == VolcanoPhase.ERUPTING) return phase.name(); + if (phase == VolcanoPhase.BUILDING) strongest = phase; + } + return strongest != null ? strongest.name() : null; + } + + public boolean isStormIncoming(RegionCoordinate coord) { + if (coord == null || isClimateEventActive(coord, ClimateEventType.LIGHTNING_STORM) + || isClimateEventActive(coord, ClimateEventType.BLIZZARD) + || isClimateEventActive(coord, ClimateEventType.SANDSTORM)) { + return false; + } + return getRegionalWeather(coord) + .map(atmosphere -> atmosphere.getThunderLevel() >= 0.25 + && atmosphere.getThunderLevel() < 0.55) + .orElse(false); + } + /** Marks a region as oceanic so it can host submarine volcanoes. */ public void markOceanicRegion(RegionCoordinate coord) { if (coord != null) oceanicRegions.add(coord);