package com.mythlane.gravityflip.physics; import com.mythlane.gravityflip.region.GravityFlipRegion; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * Thread-safe tracker that decides whether fall damage should be suppressed for a given UUID * based on (a) current in-region membership, (b) post-exit grace window. * *

Pure-data service : no Hytale runtime dependency (no ECS, no PhysicsValues), injected * via constructor into {@link GravityApplier} (populator) and * {@link FallDamageSuppressorSystem} (consumer) — no static mutable state. * *

Precedence rule (multi-region) : when {@link #markInRegion} is called, the LAST * call for a given UUID within a single tick wins. {@link GravityApplier} takes care of * passing the FIRST matched region (iteration order of {@code enabledRegions}), so for an * entity simultaneously in N regions the first-match region's {@code FallDamage} * and {@code GracePeriodMs} drive suppression. * *

State machine : *

*/ public final class FallDamageGuard { /** Entity currently inside a region (cleared on exit). */ private final ConcurrentHashMap currentRegionByUuid = new ConcurrentHashMap<>(); /** Timestamp (ms) at which the entity last exited a region. */ private final ConcurrentHashMap exitTimestampByUuid = new ConcurrentHashMap<>(); /** Region referenced at the moment of exit (used to read FallDamage + GracePeriodMs). */ private final ConcurrentHashMap regionAtExitByUuid = new ConcurrentHashMap<>(); public void markInRegion(UUID uuid, GravityFlipRegion region) { if (uuid == null || region == null) return; currentRegionByUuid.put(uuid, region); // Re-entry ⇒ discard any stale grace entry (grace is a post-exit concept). exitTimestampByUuid.remove(uuid); regionAtExitByUuid.remove(uuid); } public void markExit(UUID uuid, GravityFlipRegion region, long nowMs) { if (uuid == null || region == null) return; currentRegionByUuid.remove(uuid); exitTimestampByUuid.put(uuid, nowMs); regionAtExitByUuid.put(uuid, region); } public boolean shouldSuppressFallDamage(UUID uuid, long nowMs) { if (uuid == null) return false; GravityFlipRegion current = currentRegionByUuid.get(uuid); if (current != null) { return !current.isFallDamage(); } Long exitMs = exitTimestampByUuid.get(uuid); GravityFlipRegion exitRegion = regionAtExitByUuid.get(uuid); if (exitMs == null || exitRegion == null) return false; if (exitRegion.isFallDamage()) return false; // region allowed fall damage → no grace return (nowMs - exitMs) <= exitRegion.getGracePeriodMs(); } // ---- Test hooks / diagnostics (package-private) ---- int trackedInRegionCount() { return currentRegionByUuid.size(); } int trackedGraceCount() { return exitTimestampByUuid.size(); } }