Scale hazard damage to its interval and floor the interval at 5 ticks

The value is configured, stored and labelled as damage per second, but a pulse
applied it whole, so the real rate was damage per interval - it only matched at
the default interval of 20. A pulse now deals the share of a second it covers.
The interval is floored at 5 ticks, which also bounds how often a zone scans;
this replaces the per-tick membership check, which cost 20x the entity queries.
This commit is contained in:
KAMKEEL committed 2026-08-11 04:23:59 -04:00
1 parent 4341c15cf4
commit 1acda0e6b0
2 files changed
+20 -29

No files matched your search

@@ -107,7 +107,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
readZoneNBT(nbt, 300);
this.radius = nbt.getFloat("radius");
this.damagePerSecond = nbt.getFloat("damagePerSecond");
this.damageInterval = nbt.getInteger("damageInterval");
setDamageInterval(nbt.getInteger("damageInterval"));
this.affectsCaster = nbt.getBoolean("affectsCaster");
}
@@ -138,7 +138,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
}
public void setDamageInterval(int damageInterval) {
this.damageInterval = damageInterval;
this.damageInterval = Math.max(EntityAbilityZone.MIN_DAMAGE_INTERVAL, damageInterval);
}
public boolean isAffectsCaster() {
@@ -171,6 +171,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard {
FieldDef.row(
FieldDef.floatField("gui.dps", this::getDamagePerSecond, this::setDamagePerSecond),
FieldDef.intField("gui.interval", this::getDamageInterval, this::setDamageInterval)
.range(EntityAbilityZone.MIN_DAMAGE_INTERVAL, 2000)
),
FieldDef.boolField("ability.affectsCaster", this::isAffectsCaster, this::setAffectsCaster)
.hover("ability.hover.affectsCaster"),
@@ -23,10 +23,8 @@ import noppes.npcs.NpcDamageSource;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -157,6 +155,9 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
// HAZARD-SPECIFIC PROPERTIES
// ═══════════════════════════════════════════════════════════════════
/** Floor on how often a hazard pulses, so a small interval cannot turn into a per-tick scan. */
public static final int MIN_DAMAGE_INTERVAL = 5;
private float damagePerSecond = 1.0f;
private int damageInterval = 20;
private boolean ignoreIFrames = false;
@@ -172,9 +173,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
private transient Set<Integer> triggeredEntities = new HashSet<>();
private transient int triggerFlashTick = -1;
private transient int ticksSinceDamage = 0;
private transient Set<Integer> damagedThisTick = new HashSet<>();
/** Ticks each entity has spent inside the zone since it was last damaged, by entity id. */
private transient Map<Integer, Integer> ticksInZone = new HashMap<>();
private transient Ability sourceAbility = null;
@@ -219,6 +219,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.ownerEntityId = owner != null ? owner.getEntityId() : -1;
double groundY = Ability.findGroundLevel(worldObj, x, y, z) + GROUND_OFFSET;
this.setPosition(x, groundY, z);
this.ticksSinceDamage = damageInterval;
}
/**
@@ -293,7 +294,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
zone.shape = shape;
zone.radius = radius;
zone.damagePerSecond = damagePerSecond;
zone.damageInterval = damageInterval;
zone.damageInterval = Math.max(MIN_DAMAGE_INTERVAL, damageInterval);
zone.ignoreIFrames = ignoreIFrames;
zone.affectsCaster = affectsCaster;
zone.durationTicks = durationTicks;
@@ -496,10 +497,13 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
private void tickHazard() {
damagedThisTick.clear();
ticksSinceDamage++;
if (ticksSinceDamage < damageInterval) {
return;
}
ticksSinceDamage = 0;
// Membership is checked every tick, not once per damage interval. Sampling on the
// interval alone made the effective boundary drift by however far an entity moved in
// between, which on a large radius reads as the radius itself being wrong.
AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(
posX - radius, posY - 0.5, posZ - radius,
posX + radius, posY + zoneHeight, posZ + radius
@@ -509,35 +513,21 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
@SuppressWarnings("unchecked")
List<EntityLivingBase> entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox);
Set<Integer> insideThisTick = new HashSet<>();
for (EntityLivingBase entity : entities) {
if (owner != null && entity == owner && !affectsCaster) continue;
if (damagedThisTick.contains(entity.getEntityId())) continue;
if (entity != owner && owner instanceof EntityLivingBase && !AbilityTargetHelper.shouldAffect((EntityLivingBase) owner, entity, TargetFilter.ENEMIES, false)) continue;
if (!isInZone(entity)) continue;
int entityId = entity.getEntityId();
insideThisTick.add(entityId);
// Entities that just entered are hit immediately, then once per interval of time
// actually spent inside.
Integer elapsed = ticksInZone.get(entityId);
int ticks = elapsed == null ? damageInterval : elapsed + 1;
if (ticks < damageInterval) {
ticksInZone.put(entityId, ticks);
continue;
}
ticksInZone.put(entityId, 0);
if (damagePerSecond > 0) {
applyDamage(entity, owner, damagePerSecond);
// The configured value is damage per second, so a pulse deals the share of a
// second it covers. Applying it whole made the real rate depend on the interval.
applyDamage(entity, owner, damagePerSecond * (damageInterval / 20.0f));
}
applyEffects(entity);
damagedThisTick.add(entityId);
damagedThisTick.add(entity.getEntityId());
}
ticksInZone.keySet().retainAll(insideThisTick);
}
private boolean isInZone(EntityLivingBase entity) {
@@ -829,7 +819,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
public float getDamagePerSecond() { return damagePerSecond; }
public void setDamagePerSecond(float dps) { this.damagePerSecond = dps; }
public int getDamageInterval() { return damageInterval; }
public void setDamageInterval(int ticks) { this.damageInterval = ticks; }
public void setDamageInterval(int ticks) { this.damageInterval = Math.max(MIN_DAMAGE_INTERVAL, ticks); }
public boolean isAffectsCaster() { return affectsCaster; }
public void setAffectsCaster(boolean affects) { this.affectsCaster = affects; }