package dev.cobblesurvival.zone import com.google.gson.Gson import com.google.gson.JsonElement import dev.cobblesurvival.CobbleSurvival import net.minecraft.ChatFormatting import net.minecraft.core.Holder import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.Registries import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.server.packs.resources.ResourceManager import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener import net.minecraft.tags.TagKey import net.minecraft.util.profiling.ProfilerFiller import net.minecraft.world.effect.MobEffect import net.minecraft.world.effect.MobEffectInstance import net.minecraft.world.entity.EquipmentSlot import net.minecraft.world.item.Item import net.neoforged.neoforge.common.NeoForge import net.neoforged.neoforge.event.AddReloadListenerEvent import net.neoforged.neoforge.event.tick.ServerTickEvent import java.util.UUID /** * Zone hazards that Cold Sweat doesn't cover (roadmap Phase 3: "a poison swamp ... as area effects"): while a player * is in the zone and wears nothing from the protection tag, they keep getting the effect. Potions, milk and golden * apples still work against it. Definitions: `data/cobblesurvival/zone_hazards/.json`. */ object ZoneHazards : SimpleJsonResourceReloadListener(Gson(), "zone_hazards") { private const val CHECK_TICKS = 10 class Hazard( val zone: String, val effect: Holder, val amplifier: Int, val durationTicks: Int, val everyTicks: Int, val protection: TagKey, val warning: String?, ) var hazards: List = emptyList() private set private val warned = HashMap() fun register() { NeoForge.EVENT_BUS.addListener { it.addListener(this) } NeoForge.EVENT_BUS.addListener(::onServerTick) } override fun apply(files: Map, resources: ResourceManager, profiler: ProfilerFiller) { hazards = files.mapNotNull { (id, element) -> val json = element.asJsonObject val effectId = ResourceLocation.parse(json.get("effect").asString) val effect = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null) if (effect == null) { CobbleSurvival.LOGGER.error("Zone hazard {}: unknown effect {}", id, effectId) return@mapNotNull null } Hazard( zone = json.get("zone").asString, effect = effect, amplifier = json.get("amplifier")?.asInt ?: 0, durationTicks = json.get("duration_ticks").asInt, everyTicks = json.get("every_ticks").asInt, protection = TagKey.create(Registries.ITEM, ResourceLocation.parse(json.get("protection").asString)), warning = json.get("warning")?.asString, ) } CobbleSurvival.LOGGER.info("Loaded {} zone hazards", hazards.size) } private fun onServerTick(event: ServerTickEvent.Post) { if (hazards.isEmpty()) return if (event.server.tickCount % CHECK_TICKS != 0) return for (player in event.server.playerList.players) { if (player.isCreative || player.isSpectator) continue val zone = Zones.at(player.serverLevel(), player.blockPosition())?.id if (zone == null || hazards.none { it.zone == zone }) { warned.remove(player.uuid) continue } for (hazard in hazards) { if (hazard.zone != zone || isProtected(player, hazard)) continue // At once on entering the zone (Adrian, 2026-11-09), then renewed every every_ticks. val current = player.getEffect(hazard.effect) if (current != null && current.duration > hazard.durationTicks - hazard.everyTicks) continue player.addEffect(MobEffectInstance(hazard.effect, hazard.durationTicks, hazard.amplifier)) if (hazard.warning != null && warned[player.uuid] != zone) { warned[player.uuid] = zone player.displayClientMessage(Component.translatable(hazard.warning).withStyle(ChatFormatting.DARK_GREEN), true) } } } } fun isProtected(player: ServerPlayer, hazard: Hazard) = listOf(EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET) .any { player.getItemBySlot(it).`is`(hazard.protection) } }