Max Hits for Piece and Multi

This commit is contained in:
Kam committed 2026-02-23 14:21:55 -05:00
1 parent 21cd8e9e01
commit e4fe8c5112
6 files changed
+109 -12

No files matched your search

@@ -10,6 +10,8 @@ import noppes.npcs.api.ability.data.IEnergyCombatData;
*/
public class EnergyCombatData implements IEnergyCombatData {
public static final float MAX_EXPLOSION_RADIUS = 15.0f;
public static final int DEFAULT_MAX_HITS = 5;
public static final int MAX_HITS = 200;
public float damage = 7.0f;
public float knockback = 1.0f;
@@ -19,6 +21,7 @@ public class EnergyCombatData implements IEnergyCombatData {
public float explosionDamageFalloff = 0.5f;
public HitType hitType = HitType.SINGLE;
public int multiHitDelayTicks = 5;
public int maxHits = DEFAULT_MAX_HITS;
public EnergyCombatData() {
}
@@ -26,6 +29,12 @@ public class EnergyCombatData implements IEnergyCombatData {
public EnergyCombatData(float damage, float knockback, float knockbackUp,
boolean explosive, float explosionRadius, float explosionDamageFalloff,
HitType hitType, int multiHitDelay) {
this(damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff, hitType, multiHitDelay, DEFAULT_MAX_HITS);
}
public EnergyCombatData(float damage, float knockback, float knockbackUp,
boolean explosive, float explosionRadius, float explosionDamageFalloff,
HitType hitType, int multiHitDelay, int maxHits) {
this.damage = damage;
this.knockback = knockback;
this.knockbackUp = knockbackUp;
@@ -34,6 +43,7 @@ public class EnergyCombatData implements IEnergyCombatData {
this.explosionDamageFalloff = explosionDamageFalloff;
this.hitType = hitType;
this.multiHitDelayTicks = multiHitDelay;
this.maxHits = clampMaxHits(maxHits);
}
public EnergyCombatData(float damage, float knockback, float knockbackUp,
@@ -106,6 +116,14 @@ public class EnergyCombatData implements IEnergyCombatData {
this.explosionDamageFalloff = explosionDamageFalloff;
}
public int getMaxHits() {
return maxHits;
}
public void setMaxHits(int maxHits) {
this.maxHits = clampMaxHits(maxHits);
}
public void writeNBT(NBTTagCompound nbt) {
nbt.setFloat("damage", damage);
nbt.setFloat("knockback", knockback);
@@ -115,6 +133,7 @@ public class EnergyCombatData implements IEnergyCombatData {
nbt.setFloat("explosionDamageFalloff", explosionDamageFalloff);
nbt.setInteger("hitType", hitType.ordinal());
nbt.setInteger("multiHitDelayTicks", multiHitDelayTicks);
nbt.setInteger("maxHits", maxHits);
}
public void readNBT(NBTTagCompound nbt) {
@@ -126,6 +145,7 @@ public class EnergyCombatData implements IEnergyCombatData {
explosionDamageFalloff = nbt.hasKey("explosionDamageFalloff") ? nbt.getFloat("explosionDamageFalloff") : 0.5f;
hitType = HitType.fromOrdinal(nbt.hasKey("hitType") ? nbt.getInteger("hitType") : 0);
multiHitDelayTicks = nbt.hasKey("multiHitDelayTicks") ? nbt.getInteger("multiHitDelayTicks") : 5;
maxHits = nbt.hasKey("maxHits") ? nbt.getInteger("maxHits") : DEFAULT_MAX_HITS;
// Sanitize
if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 7.0f;
@@ -133,6 +153,7 @@ public class EnergyCombatData implements IEnergyCombatData {
if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius) || explosionRadius < 0) explosionRadius = 3.0f;
explosionRadius = clampExplosionRadius(explosionRadius);
if (multiHitDelayTicks < 1) multiHitDelayTicks = 1;
maxHits = clampMaxHits(maxHits);
}
private static float clampExplosionRadius(float explosionRadius) {
@@ -140,11 +161,16 @@ public class EnergyCombatData implements IEnergyCombatData {
return Math.max(0.0f, Math.min(MAX_EXPLOSION_RADIUS, explosionRadius));
}
private static int clampMaxHits(int maxHits) {
return Math.max(1, Math.min(MAX_HITS, maxHits));
}
public EnergyCombatData copy() {
EnergyCombatData copy = new EnergyCombatData(damage, knockback, knockbackUp,
explosive, explosionRadius, explosionDamageFalloff);
copy.hitType = hitType;
copy.multiHitDelayTicks = multiHitDelayTicks;
copy.maxHits = maxHits;
return copy;
}
}
@@ -685,6 +685,14 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
this.combatData.multiHitDelayTicks = Math.max(1, delay);
}
public int getMaxHits() {
return combatData.getMaxHits();
}
public void setMaxHits(int maxHits) {
combatData.setMaxHits(maxHits);
}
// Lifespan data
@Override
public float getMaxDistance() {
@@ -832,6 +840,10 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
.range(1, 200)
.visibleWhen(() -> getHitType() == HitType.MULTI.ordinal())
.hover("ability.hover.multiHitDelay"));
defs.add(FieldDef.intField("ability.maxHits", this::getMaxHits, this::setMaxHits)
.range(1, EnergyCombatData.MAX_HITS)
.visibleWhen(() -> getHitType() != HitType.SINGLE.ordinal())
.hover("ability.hover.maxHits"));
// Type-specific fields first
addTypeDefinitions(defs);
@@ -124,6 +124,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// ==================== STATE ====================
protected boolean hasHit = false;
protected int hitCount = 0;
protected final Set<Integer> hitOnceEntities = new HashSet<Integer>();
protected final Map<Integer, Integer> lastHitTickByEntity = new HashMap<Integer, Integer>();
protected static final int BARRIER_IMPACT_PAUSE_TICKS = 10;
@@ -508,6 +509,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
hitOnceEntities.clear();
lastHitTickByEntity.clear();
hitCount = 0;
return true;
}
@@ -943,13 +945,12 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// Using feet position (target.posY) causes explosions at impact height to miss
// entities standing below — e.g. a projectile hitting at eye height (~1.62 above feet)
// would measure dist=1.62, failing a radius<=1.6 check despite a direct hit.
double closestX = Math.max(target.boundingBox.minX, Math.min(posX, target.boundingBox.maxX));
double closestY = Math.max(target.boundingBox.minY, Math.min(posY, target.boundingBox.maxY));
double closestZ = Math.max(target.boundingBox.minZ, Math.min(posZ, target.boundingBox.maxZ));
Vec3 closest = closestPointOnBoundingBox(target.boundingBox, posX, posY, posZ);
if (closest == null) continue;
double dist = Math.sqrt(
(closestX - posX) * (closestX - posX) +
(closestY - posY) * (closestY - posY) +
(closestZ - posZ) * (closestZ - posZ)
(closest.xCoord - posX) * (closest.xCoord - posX) +
(closest.yCoord - posY) * (closest.yCoord - posY) +
(closest.zCoord - posZ) * (closest.zCoord - posZ)
);
if (dist <= explosionRad) {
@@ -970,6 +971,17 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
return shouldIgnoreEntity(target);
}
/**
* Clamp a point to the nearest point inside the provided AABB.
*/
protected Vec3 closestPointOnBoundingBox(AxisAlignedBB box, double x, double y, double z) {
if (box == null) return null;
double closestX = Math.max(box.minX, Math.min(x, box.maxX));
double closestY = Math.max(box.minY, Math.min(y, box.maxY));
double closestZ = Math.max(box.minZ, Math.min(z, box.maxZ));
return Vec3.createVectorHelper(closestX, closestY, closestZ);
}
/**
* Spawn a packet-driven client preview so explosions are visible as geometry,
* not only as particles.
@@ -1861,8 +1873,17 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
combatData.multiHitDelayTicks = Math.max(1, delayTicks);
}
public int getMaxHits() {
return combatData.getMaxHits();
}
public void setMaxHits(int maxHits) {
combatData.setMaxHits(maxHits);
}
protected boolean canHitEntityNow(EntityLivingBase entity) {
if (entity == null) return false;
if (combatData.hitType != HitType.SINGLE && hasReachedMaxHits()) return false;
int entityId = entity.getEntityId();
switch (combatData.hitType) {
@@ -1884,10 +1905,17 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
int entityId = entity.getEntityId();
hitOnceEntities.add(entityId);
lastHitTickByEntity.put(entityId, ticksExisted);
hitCount++;
}
protected boolean hasReachedMaxHits() {
if (combatData.hitType == HitType.SINGLE) return false;
return hitCount >= combatData.getMaxHits();
}
protected boolean shouldTerminateAfterHit() {
return combatData.hitType == HitType.SINGLE;
if (combatData.hitType == HitType.SINGLE) return true;
return hasReachedMaxHits();
}
/**
@@ -1931,12 +1959,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
recordEntityHit(entity);
if (isExplosive()) {
Vec3 impactPoint = resolveEntityImpactPoint(entity, impactX, impactY, impactZ);
double oldX = posX;
double oldY = posY;
double oldZ = posZ;
posX = impactX;
posY = impactY;
posZ = impactZ;
posX = impactPoint.xCoord;
posY = impactPoint.yCoord;
posZ = impactPoint.zCoord;
doExplosion();
posX = oldX;
posY = oldY;
@@ -1954,6 +1983,26 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
return false;
}
/**
* Resolve a stable impact point on the target so explosive collisions use actual contact.
*/
protected Vec3 resolveEntityImpactPoint(EntityLivingBase entity, double impactX, double impactY, double impactZ) {
if (entity == null || entity.boundingBox == null) {
return Vec3.createVectorHelper(impactX, impactY, impactZ);
}
Vec3 segmentStart = Vec3.createVectorHelper(posX, posY, posZ);
Vec3 segmentEnd = Vec3.createVectorHelper(impactX, impactY, impactZ);
AxisAlignedBB expandedBox = entity.boundingBox.expand(1.0e-4, 1.0e-4, 1.0e-4);
MovingObjectPosition intercept = expandedBox.calculateIntercept(segmentStart, segmentEnd);
if (intercept != null && intercept.hitVec != null) {
return intercept.hitVec;
}
Vec3 closest = closestPointOnBoundingBox(entity.boundingBox, impactX, impactY, impactZ);
return closest != null ? closest : Vec3.createVectorHelper(impactX, impactY, impactZ);
}
/**
* Scan entities in a hitbox and process the first/each valid hit based on hit mode.
*/
@@ -177,6 +177,14 @@ public class ScriptEnergyProjectile<T extends EntityEnergyProjectile> extends Sc
entity.setMultiHitDelayTicks(delayTicks);
}
public int getMaxHits() {
return entity.getMaxHits();
}
public void setMaxHits(int maxHits) {
entity.setMaxHits(maxHits);
}
// ==================== MOVEMENT ====================
public float getSpeed() {