diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java index 871d48461..7388d7ec2 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java @@ -34,7 +34,7 @@ public class RenderEnergyDome extends RenderEnergyBarrier { // Hit flash effect float flashAlpha = computeFlashAlpha(dome); - float innerScale = 0.95f; + float innerScale = 1.0f; // Render outer dome (translucent glow) if (dome.isOuterColorEnabled()) { diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java index 279c6902d..38bdd57b2 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyPanel.java @@ -48,7 +48,7 @@ public class RenderEnergyPanel extends RenderEnergyBarrier { GL11.glPushMatrix(); GL11.glScalef(scale, scale, scale); - float innerScale = 0.9f; + float innerScale = 1.0f; float panelThickness = 0.1f; // Render outer panel (translucent glow) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java index ef9bcff57..6f3610376 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java @@ -31,6 +31,11 @@ public class EnergyBarrierData { public boolean meleeEnabled = false; public float meleeDamageMultiplier = 1.0f; + // Jail settings (dome only - captures entities inside at spawn) + public boolean jailEnabled = false; + public boolean damageProtection = false; + public float jailThreshold = 0.9f; + /** * Per-projectile-type damage multipliers. * Key is the ability typeId (e.g., "ability.cnpc.orb", "ability.cnpc.beam"). @@ -144,6 +149,9 @@ public class EnergyBarrierData { nbt.setInteger("barrierKnockbackTarget", knockbackTarget); nbt.setBoolean("barrierMelee", meleeEnabled); nbt.setFloat("barrierMeleeMult", meleeDamageMultiplier); + nbt.setBoolean("barrierJail", jailEnabled); + nbt.setBoolean("barrierDmgProtect", damageProtection); + nbt.setFloat("barrierJailThreshold", jailThreshold); NBTTagCompound multNbt = new NBTTagCompound(); for (Map.Entry entry : damageMultipliers.entrySet()) { @@ -163,6 +171,9 @@ public class EnergyBarrierData { knockbackTarget = nbt.hasKey("barrierKnockbackTarget") ? nbt.getInteger("barrierKnockbackTarget") : 0; meleeEnabled = nbt.hasKey("barrierMelee") && nbt.getBoolean("barrierMelee"); meleeDamageMultiplier = nbt.hasKey("barrierMeleeMult") ? nbt.getFloat("barrierMeleeMult") : 1.0f; + jailEnabled = nbt.hasKey("barrierJail") && nbt.getBoolean("barrierJail"); + damageProtection = nbt.hasKey("barrierDmgProtect") && nbt.getBoolean("barrierDmgProtect"); + jailThreshold = nbt.hasKey("barrierJailThreshold") ? nbt.getFloat("barrierJailThreshold") : 0.9f; damageMultipliers.clear(); if (nbt.hasKey("barrierMultipliers")) { @@ -183,6 +194,9 @@ public class EnergyBarrierData { copy.knockbackTarget = knockbackTarget; copy.meleeEnabled = meleeEnabled; copy.meleeDamageMultiplier = meleeDamageMultiplier; + copy.jailEnabled = jailEnabled; + copy.damageProtection = damageProtection; + copy.jailThreshold = jailThreshold; copy.damageMultipliers = new HashMap<>(damageMultipliers); return copy; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java index 78a317cc2..cdb8f6f2f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java @@ -135,5 +135,14 @@ public class AbilityDome extends AbilityEnergyBarrier { .range(1.0f, 50.0f)); defs.add(FieldDef.boolField("ability.followCaster", this::isFollowCaster, this::setFollowCaster) .hover("ability.hover.followCaster")); + + // Jail & protection section (dome-only: only enclosures can jail) + defs.add(FieldDef.section("ability.section.jail")); + defs.add(FieldDef.boolField("ability.jailEnabled", this::isJailEnabled, this::setJailEnabled) + .hover("ability.hover.jailEnabled")); + defs.add(FieldDef.floatField("ability.jailThreshold", this::getJailThreshold, this::setJailThreshold) + .range(0.1f, 1.0f).visibleWhen(this::isJailEnabled)); + defs.add(FieldDef.boolField("ability.damageProtection", this::isDamageProtection, this::setDamageProtection) + .hover("ability.hover.damageProtection")); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java index f78cf5654..d3b543885 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyBarrier.java @@ -232,6 +232,31 @@ public abstract class AbilityEnergyBarrier extends AbilityEnergy { barrierData.meleeDamageMultiplier = mult; } + // Jail data + public boolean isJailEnabled() { + return barrierData.jailEnabled; + } + + public void setJailEnabled(boolean enabled) { + barrierData.jailEnabled = enabled; + } + + public boolean isDamageProtection() { + return barrierData.damageProtection; + } + + public void setDamageProtection(boolean enabled) { + barrierData.damageProtection = enabled; + } + + public float getJailThreshold() { + return barrierData.jailThreshold; + } + + public void setJailThreshold(float threshold) { + barrierData.jailThreshold = Math.max(0.1f, Math.min(1.0f, threshold)); + } + // ==================== GUI ==================== @SideOnly(Side.CLIENT) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 736330cdd..8f9df2bb5 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -382,17 +382,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { protected boolean checkBarrierCollision() { if (currentLength <= 0 || fullyExtended) return false; - // Search box covers the full laser line plus barrier search range - double searchRange = MAX_ENTITY_RADIUS + 5; - double minX = Math.min(startX, endX) - searchRange; - double minY = Math.min(startY, endY) - searchRange; - double minZ = Math.min(startZ, endZ) - searchRange; - double maxX = Math.max(startX, endX) + searchRange; - double maxY = Math.max(startY, endY) + searchRange; - double maxZ = Math.max(startZ, endZ) + searchRange; - AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox(minX, minY, minZ, maxX, maxY, maxZ); - - List barriers = worldObj.getEntitiesWithinAABB(EntityEnergyBarrier.class, searchBox); + List barriers = EntityEnergyBarrier.getActiveBarriers(worldObj); for (EntityEnergyBarrier barrier : barriers) { if (barrier.isDead || barrier.isCharging()) continue; if (barrier.getOwnerEntityId() == this.ownerEntityId) continue; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java index a5d263075..1027fe93e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java @@ -5,12 +5,18 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; import net.minecraft.world.World; import noppes.npcs.EventHooks; import noppes.npcs.entity.EntityNPCInterface; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + /** * Base class for energy barrier entities (Dome, Panel). * Provides shared health, damage, hit flash, knockback target, @@ -19,6 +25,35 @@ import noppes.npcs.entity.EntityNPCInterface; */ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { + // ==================== ACTIVE BARRIER REGISTRY ==================== + private static final List> activeBarriers = new ArrayList<>(); + private boolean tracked = false; + + public static void trackBarrier(EntityEnergyBarrier barrier) { + activeBarriers.add(new WeakReference<>(barrier)); + } + + /** + * Get all living barriers in the given world. Auto-cleans dead refs. + */ + public static List getActiveBarriers(World world) { + List result = new ArrayList<>(); + Iterator> it = activeBarriers.iterator(); + while (it.hasNext()) { + EntityEnergyBarrier b = it.next().get(); + if (b == null || b.isDead) { + it.remove(); + } else if (b.worldObj == world) { + result.add(b); + } + } + return result; + } + + public static void clearAllBarriers() { + activeBarriers.clear(); + } + // ==================== BARRIER PROPERTIES ==================== protected EnergyBarrierData barrierData = new EnergyBarrierData(); protected float currentHealth; @@ -63,6 +98,65 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { */ protected abstract void knockbackEntities(); + /** + * Get the maximum extent of this barrier for distance pre-filtering. + * Dome returns radius, Panel returns max(width, height) / 2. + */ + public abstract float getMaxExtent(); + + /** + * Check if a generic (non-CNPC+) projectile is incoming toward this barrier. + * Uses the same geometric logic as isIncomingProjectile but with raw position data. + * + * @param posX, posY, posZ Current (or predicted) position of the projectile + * @param motionX, motionY, motionZ Current velocity of the projectile + * @param prevPosX, prevPosY, prevPosZ Previous position of the projectile + * @param ownerEntityId Entity ID of the projectile's shooter + */ + public abstract boolean isIncomingGenericProjectile( + double posX, double posY, double posZ, + double motionX, double motionY, double motionZ, + double prevPosX, double prevPosY, double prevPosZ, + int ownerEntityId); + + // ==================== GENERIC PROJECTILE HIT ==================== + + /** + * Apply damage from a generic (non-CNPC+) projectile to this barrier. + * Uses the typeId for damage multiplier lookup (e.g. "dbc.ki_attack"). + * + * @param projectileEntity The entity that hit the barrier (for context) + * @param damage Base damage to apply + * @param typeId Type identifier for damage multiplier lookup + * @return true if the barrier absorbed the hit (projectile should be destroyed) + */ + public boolean onGenericProjectileHit(Entity projectileEntity, float damage, String typeId) { + float multiplier = barrierData.getMultiplier(typeId); + float finalDamage = damage * multiplier; + + if (!worldObj.isRemote) { + float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, finalDamage); + if (eventDamage < 0) return false; + finalDamage = eventDamage; + } + + triggerHitFlash(); + + if (!barrierData.useHealth) { + return true; + } + + currentHealth -= finalDamage; + syncHealthPercent(); + + if (currentHealth <= 0) { + onBarrierDestroyed(); + this.setDead(); + } + + return true; + } + // ==================== PROJECTILE HIT ==================== /** @@ -146,8 +240,12 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { */ protected boolean updateBarrierTick() { if (!worldObj.isRemote) { - // Fire spawned event on first tick + // Track barrier and fire spawned event on first tick if (ticksAlive == 1) { + if (!tracked) { + trackBarrier(this); + tracked = true; + } EventHooks.onEnergyBarrierSpawned(this, ownerEntityId); } @@ -247,6 +345,83 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { return AbilityTargetHelper.isAlly((EntityLivingBase) owner, entity); } + // ==================== CONTAINMENT & PROTECTION ==================== + + /** + * Check if an entity is geometrically inside this barrier's protected zone. + * Override in subclasses (dome checks sphere containment). + */ + public boolean isEntityInside(Entity entity) { + return false; + } + + /** + * Check if a damage source originates from outside this barrier. + * Uses geometry-specific containment check via isEntityInside(). + */ + public boolean isDamageFromOutside(DamageSource source) { + Entity attacker = source.getEntity(); + if (attacker == null) return true; // Environmental damage treated as outside + return !isEntityInside(attacker); + } + + /** + * Find a barrier that would protect the given entity from outside damage. + * Returns the barrier if: damageProtection is enabled AND entity is inside. + * Any entity inside a dome with damageProtection gets redirected damage. + */ + public static EntityEnergyBarrier getProtectingBarrier(Entity entity) { + if (entity == null || entity.worldObj == null) return null; + List barriers = getActiveBarriers(entity.worldObj); + for (EntityEnergyBarrier barrier : barriers) { + if (barrier.isDead) continue; + if (!barrier.barrierData.damageProtection) continue; + if (barrier.isEntityInside(entity)) { + return barrier; + } + } + return null; + } + + /** + * Absorb damage from an external source. Reduces barrier health directly. + * Used by damage protection to redirect attacks on inside entities to the barrier. + * + * @param amount The damage amount to absorb + * @return true if damage was absorbed (barrier still alive or just destroyed) + */ + public boolean absorbDamage(float amount) { + if (worldObj.isRemote) return false; + + triggerHitFlash(); + + if (!barrierData.useHealth) { + return true; // Duration-only: absorb but no health loss + } + + currentHealth -= amount; + syncHealthPercent(); + + if (currentHealth <= 0) { + onBarrierDestroyed(); + this.setDead(); + } + return true; + } + + /** + * Teleport an entity to a position. Uses network handler for players + * to ensure reliable client synchronization. + */ + protected void teleportEntity(EntityLivingBase ent, double x, double y, double z) { + if (ent instanceof EntityPlayerMP) { + EntityPlayerMP player = (EntityPlayerMP) ent; + player.playerNetServerHandler.setPlayerLocation(x, y, z, player.rotationYaw, player.rotationPitch); + } else { + ent.setPosition(x, y, z); + } + } + // ==================== GETTERS ==================== public EnergyBarrierData getBarrierData() { diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index dbe8d4db6..2d2a0ce34 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -45,6 +45,27 @@ public class EntityEnergyDome extends EntityEnergyBarrier { this.setPosition(x, y, z); } + protected void setSize(float width, float height){ + super.setSize(width, height); + + this.ySize = 0.0F; + this.yOffset = this.height * 0.5F; + } + + + // ==================== POSITION / BOUNDING BOX ==================== + + /** + * Override setPosition to maintain dome-sized bounding box. + * MC's default setPosition() resets BB based on width/height fields. + * Network sync calls setPosition(), which would shrink the BB. + * This ensures melee targeting always works against the full dome sphere. + */ + @Override + public void setPosition(double x, double y, double z) { + super.setPosition(x, y, z); + } + @Override public void onUpdate() { this.prevPosX = this.posX; @@ -70,6 +91,8 @@ public class EntityEnergyDome extends EntityEnergyBarrier { chargeTick++; float progress = getChargeProgress(); this.domeRadius = targetDomeRadius * progress; + // Refresh BB for current charge radius + this.setPosition(posX, posY, posZ); return; // Don't tick duration/death during charging } @@ -77,7 +100,7 @@ public class EntityEnergyDome extends EntityEnergyBarrier { if (updateBarrierTick()) return; - // Knockback entities inside the dome + // Knockback velocity push every tick if (!worldObj.isRemote && barrierData.knockbackEnabled) { knockbackEntities(); } @@ -86,17 +109,55 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== INCOMING CHECK ==================== /** - * Check if a projectile at the given position is entering this dome from outside. - * Only blocks incoming projectiles (dot product check). + * Swept ray-sphere intersection test. + * Tests if the line segment from prevPos to currPos crosses the dome sphere boundary + * from outside. Handles fast projectiles that skip through in a single tick. + * + * @return true if the segment enters the sphere from outside */ + private boolean isIncomingRay( + double currX, double currY, double currZ, + double prevX, double prevY, double prevZ, + int projOwnerEntityId) + { + if (isCharging()) return false; + if (projOwnerEntityId == this.ownerEntityId) return false; + + // Ray: P(t) = prev + t * (curr - prev), t in [0, 1] + double rayDirX = currX - prevX; + double rayDirY = currY - prevY; + double rayDirZ = currZ - prevZ; + + // Vector from dome center to ray origin (prevPos) + double ocX = prevX - this.posX; + double ocY = prevY - this.posY; + double ocZ = prevZ - this.posZ; + + double r = domeRadius; + + // Quadratic: a*t^2 + b*t + c = 0 + double a = rayDirX * rayDirX + rayDirY * rayDirY + rayDirZ * rayDirZ; + double b = 2.0 * (ocX * rayDirX + ocY * rayDirY + ocZ * rayDirZ); + double c = (ocX * ocX + ocY * ocY + ocZ * ocZ) - r * r; + + // c > 0: prevPos is outside sphere; c <= 0: inside (don't block) + if (c <= 0) return false; + + if (a < 1e-10) return false; // No movement + + double discriminant = b * b - 4.0 * a * c; + if (discriminant < 0) return false; // Ray misses sphere entirely + + double sqrtDisc = Math.sqrt(discriminant); + double t1 = (-b - sqrtDisc) / (2.0 * a); // Entry point (first intersection) + + // Entry must be within this tick's movement segment [0, 1] + return t1 >= 0.0 && t1 <= 1.0; + } + @Override public boolean isIncomingProjectile(EntityEnergyProjectile projectile) { - // Don't block during charging phase - if (isCharging()) return false; - // Don't block projectiles from the dome's owner - if (projectile.getOwnerEntityId() == this.ownerEntityId) return false; - - // Don't block projectiles from same-faction NPCs + // Faction check: don't block same-faction NPC projectiles Entity owner = getOwnerEntity(); Entity projOwner = projectile.getOwnerEntity(); if (owner instanceof noppes.npcs.entity.EntityNPCInterface && projOwner instanceof noppes.npcs.entity.EntityNPCInterface) { @@ -105,29 +166,29 @@ public class EntityEnergyDome extends EntityEnergyBarrier { } } - double dx = projectile.posX - this.posX; - double dy = projectile.posY - this.posY; - double dz = projectile.posZ - this.posZ; - double dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + double prevX = projectile.posX - projectile.motionX; + double prevY = projectile.posY - projectile.motionY; + double prevZ = projectile.posZ - projectile.motionZ; - // Check if projectile is outside dome radius - if (dist > domeRadius) return false; + return isIncomingRay( + projectile.posX, projectile.posY, projectile.posZ, + prevX, prevY, prevZ, + projectile.getOwnerEntityId()); + } - // Approximate the projectile's position before last tick's movement. - // checkBarrierCollision runs BEFORE updateProjectile, so posX is post-last-movement. - // posX - motionX gives the pre-movement position (works for moveEntity and setPosition). - double prevDx = (projectile.posX - projectile.motionX) - this.posX; - double prevDy = (projectile.posY - projectile.motionY) - this.posY; - double prevDz = (projectile.posZ - projectile.motionZ) - this.posZ; - double prevDist = Math.sqrt(prevDx * prevDx + prevDy * prevDy + prevDz * prevDz); + @Override + public boolean isIncomingGenericProjectile( + double posX, double posY, double posZ, + double motionX, double motionY, double motionZ, + double prevPosX, double prevPosY, double prevPosZ, + int ownerEntityId) + { + return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId); + } - // Only block projectiles that crossed the dome boundary from outside. - // Projectiles spawned inside (prevDist < radius) pass through freely. - if (prevDist < domeRadius) return false; - - // Projectile entered from outside — check it's still moving inward - double dot = dx * projectile.motionX + dy * projectile.motionY + dz * projectile.motionZ; - return dot < 0; + @Override + public float getMaxExtent() { + return domeRadius; } // ==================== CHARGING ==================== @@ -147,19 +208,41 @@ public class EntityEnergyDome extends EntityEnergyBarrier { setCharging(false); } - // ==================== KNOCKBACK ==================== + // ==================== KNOCKBACK (DBO-style motion prediction) ==================== /** - * Push entities away from the dome surface. - * Entities outside are pushed outward, entities inside are pushed inward (containment). + * DBO Ki Shield style knockback — unified algorithm with geometric jail: + *

+ * When jailEnabled = false (default): + * - OUTSIDE entities: predict future position, if it would cross into dome push outward. + * If close and approaching, apply soft outward push. Full motion replacement. + * - INSIDE entities: NOT affected (free movement). Dome only blocks entry. + *

+ * When jailEnabled = true: + * - OUTSIDE entities: pushed outward (same as above, prevents entry). + * - INSIDE entities: pushed inward when near edge (geometric containment). + * Any entity inside stays inside, any entity outside stays outside. + *

+ * For moving domes (followCaster), dome velocity is accounted for by using + * relative velocity (entity motion minus dome motion) for predictions. */ @Override @SuppressWarnings("unchecked") protected void knockbackEntities() { - float margin = 1.0f; + float margin = 2.0f; + float r = domeRadius; + float strength = barrierData.knockbackStrength; + boolean jail = barrierData.jailEnabled; + float jailThresh = barrierData.jailThreshold; + + // Dome velocity for moving domes (followCaster) + double domeVelX = posX - prevPosX; + double domeVelY = posY - prevPosY; + double domeVelZ = posZ - prevPosZ; + AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( - posX - domeRadius - margin, posY - domeRadius - margin, posZ - domeRadius - margin, - posX + domeRadius + margin, posY + domeRadius + margin, posZ + domeRadius + margin + posX - r - margin, posY - r - margin, posZ - r - margin, + posX + r + margin, posY + r + margin, posZ + r + margin ); List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); @@ -168,31 +251,102 @@ public class EntityEnergyDome extends EntityEnergyBarrier { if (!isKnockbackTarget(ent)) continue; if (isAllyOfOwner(ent)) continue; + // Current position relative to dome center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; double dz = ent.posZ - posZ; double dist = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (dist < 0.01) continue; - // Only affect entities near the dome surface - if (dist < domeRadius + margin && dist > domeRadius - margin) { - double pushStrength = barrierData.knockbackStrength * 0.5; - double pushDir = dist >= domeRadius ? 1.0 : -1.0; // Outside = outward, Inside = inward - ent.addVelocity( - (dx / dist) * pushStrength * pushDir, - 0.05 * pushDir, - (dz / dist) * pushStrength * pushDir - ); - ent.velocityChanged = true; + // Normalized direction from dome center toward entity (outward) + double nx = dx / dist; + double ny = dy / dist; + double nz = dz / dist; + + // Entity velocity relative to dome (accounts for moving domes) + double relVelX = ent.motionX - domeVelX; + double relVelY = ent.motionY - domeVelY; + double relVelZ = ent.motionZ - domeVelZ; + + boolean isInside = dist < r; + + if (isInside && jail) { + // --- INSIDE + JAIL ENABLED: push toward center when near dome edge --- + if (dist > r * jailThresh) { + double overshoot = dist - r * jailThresh; + double force = (overshoot + 0.1) * strength * 0.2; + // Set motion to dome velocity + inward push (keeps entity moving with dome) + ent.motionX = domeVelX - nx * force; + ent.motionY = domeVelY - ny * force; + ent.motionZ = domeVelZ - nz * force; + ent.velocityChanged = true; + } else { + // Predict if entity would escape next tick (relative to dome) + double futDx = dx + relVelX; + double futDy = dy + relVelY; + double futDz = dz + relVelZ; + double futDist = Math.sqrt(futDx * futDx + futDy * futDy + futDz * futDz); + if (futDist >= r) { + // Would escape: cancel outward motion, match dome velocity + double radialVel = relVelX * nx + relVelY * ny + relVelZ * nz; + if (radialVel > 0) { + ent.motionX -= radialVel * nx; + ent.motionY -= radialVel * ny; + ent.motionZ -= radialVel * nz; + ent.velocityChanged = true; + } + } + } + } else if (!isInside) { + // --- OUTSIDE: push outward (prevent entry) --- + + // Predicted future distance from center (relative to dome movement) + double futDx = dx + relVelX; + double futDy = dy + relVelY; + double futDz = dz + relVelZ; + double futDist = Math.sqrt(futDx * futDx + futDy * futDy + futDz * futDz); + + // Radial velocity relative to dome (positive = moving away) + double radialVel = relVelX * nx + relVelY * ny + relVelZ * nz; + + if (futDist < r) { + // Would cross INTO dome: hard push outward proportional to overshoot + double overshoot = r - futDist; + double force = (overshoot * 2.0 + 0.05) * strength * 0.1; + ent.motionX = domeVelX + nx * force; + ent.motionY = domeVelY + ny * force * 0.5; + ent.motionZ = domeVelZ + nz * force; + ent.velocityChanged = true; + } else if (radialVel < 0 && dist < r + margin) { + // Close and approaching: soft push outward proportional to proximity + double proximity = 1.0 - ((dist - r) / margin); + double force = proximity * strength * 0.1; + ent.motionX = domeVelX + nx * force; + ent.motionY = domeVelY + ny * force * 0.5; + ent.motionZ = domeVelZ + nz * force; + ent.velocityChanged = true; + } } + // else: INSIDE + no jail → do nothing (free movement) } } + // ==================== CONTAINMENT CHECK ==================== + + @Override + public boolean isEntityInside(Entity entity) { + if (entity == null) return false; + double dx = entity.posX - posX; + double dy = (entity.posY + entity.height * 0.5) - posY; + double dz = entity.posZ - posZ; + return dx * dx + dy * dy + dz * dz < domeRadius * domeRadius; + } + // ==================== MELEE (spherical check) ==================== /** * Reject melee hits that land on the cubic bounding box but are outside the actual sphere. + * Uses distance from the sphere surface (not center) for accurate spherical rejection. */ @Override public boolean attackEntityFrom(DamageSource source, float amount) { @@ -201,10 +355,10 @@ public class EntityEnergyDome extends EntityEnergyBarrier { double dx = attacker.posX - this.posX; double dy = (attacker.posY + attacker.height * 0.5) - this.posY; double dz = attacker.posZ - this.posZ; - double distSq = dx * dx + dy * dy + dz * dz; - // Allow hit only if attacker is within sphere + melee reach margin - double maxDist = domeRadius + 5.0; - if (distSq > maxDist * maxDist) return false; + double dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + double surfaceDist = Math.abs(dist - domeRadius); + // Allow hit only if attacker is within melee reach of the sphere surface + if (surfaceDist > 5.0) return false; } return super.attackEntityFrom(source, amount); } @@ -212,14 +366,13 @@ public class EntityEnergyDome extends EntityEnergyBarrier { // ==================== BOUNDING BOX ==================== /** - * Expands the ray-trace targeting area so players can melee-hit the dome - * from anywhere near its surface, not just the tiny 1x1 center. - * This is used by EntityRenderer.getMouseOver() to expand the entity BB - * for crosshair targeting without modifying the actual bounding box. + * Small expansion on top of the dome-sized bounding box for comfortable + * melee targeting. The actual BB is already dome-sized (set in onUpdate), + * so this only adds a small reach margin for crosshair ray-testing. */ @Override public float getCollisionBorderSize() { - return domeRadius; + return 1.0f; } @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index 19bc1f136..cd33f6460 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -69,6 +69,30 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { } } + // ==================== POSITION / BOUNDING BOX ==================== + + /** + * Override setPosition to maintain panel-sized bounding box. + * MC's default setPosition() resets BB based on width/height fields. + * Network sync calls setPosition(), which would shrink the BB. + * This ensures melee targeting always works against the full panel surface. + */ + @Override + public void setPosition(double x, double y, double z) { + this.posX = x; + this.posY = y; + this.posZ = z; + if (panelData != null) { + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + float extent = Math.max(halfW, 0.5f); + this.boundingBox.setBounds(x - extent, y - halfH, z - extent, x + extent, y + halfH, z + extent); + } else { + float f = this.width / 2.0F; + this.boundingBox.setBounds(x - f, y, z - f, x + f, y + this.height, z + f); + } + } + @Override public void onUpdate() { this.prevPosX = this.posX; @@ -95,7 +119,7 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { if (updateBarrierTick()) return; - // Knockback (not during launched mode) + // Knockback velocity push every tick (not during launched mode) if (!worldObj.isRemote && barrierData.knockbackEnabled && mode != PanelMode.LAUNCHED) { knockbackEntities(); } @@ -205,16 +229,89 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { // ==================== INCOMING CHECK ==================== /** - * Check if a projectile hits this panel surface. - * Only blocks incoming projectiles (not from the panel's owner). + * Swept ray-plane intersection test for the panel. + * Tests if the line segment from prevPos to currPos crosses the panel plane + * within its width/height bounds. Handles fast projectiles that skip through + * the thin panel in a single tick. + * + * @return true if the segment crosses the panel from either side */ + private boolean isIncomingRay( + double currX, double currY, double currZ, + double prevX, double prevY, double prevZ, + int projOwnerEntityId) + { + if (isCharging()) return false; + if (projOwnerEntityId == this.ownerEntityId) return false; + + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + float panelThickness = 0.5f; + + float yawRad = (float) Math.toRadians(panelYaw); + double normalX = -Math.sin(yawRad); + double normalZ = Math.cos(yawRad); + double cos = Math.cos(yawRad); + double sin = Math.sin(yawRad); + + // Signed distance from prevPos and currPos to panel plane + double relPrevX = prevX - this.posX; + double relPrevY = prevY - this.posY; + double relPrevZ = prevZ - this.posZ; + double prevDist = relPrevX * normalX + relPrevZ * normalZ; + + double relCurrX = currX - this.posX; + double relCurrY = currY - this.posY; + double relCurrZ = currZ - this.posZ; + double currDist = relCurrX * normalX + relCurrZ * normalZ; + + // Check if segment crosses the plane (sign change) + if (prevDist * currDist > 0) { + // Both on same side — check if within thickness (slow projectile fallback) + if (Math.abs(currDist) <= panelThickness) { + double localRight = relCurrX * cos + relCurrZ * sin; + if (Math.abs(localRight) > halfW) return false; + if (Math.abs(relCurrY) > halfH) return false; + // Check incoming direction + double motX = currX - prevX; + double motZ = currZ - prevZ; + double dot = motX * normalX + motZ * normalZ; + if (currDist > 0 && dot >= 0) return false; + if (currDist < 0 && dot <= 0) return false; + return true; + } + return false; + } + + // Segment crosses plane — compute intersection parameter t + double rayDirX = currX - prevX; + double rayDirZ = currZ - prevZ; + double denom = rayDirX * normalX + rayDirZ * normalZ; + if (Math.abs(denom) < 1e-10) return false; // Parallel to plane + + double t = -prevDist / denom; + if (t < 0.0 || t > 1.0) return false; + + // Compute intersection point + double hitX = prevX + (currX - prevX) * t; + double hitY = prevY + (currY - prevY) * t; + double hitZ = prevZ + (currZ - prevZ) * t; + + // Transform hit point to panel-local space and check bounds + double relHitX = hitX - this.posX; + double relHitY = hitY - this.posY; + double relHitZ = hitZ - this.posZ; + + double localRight = relHitX * cos + relHitZ * sin; + if (Math.abs(localRight) > halfW) return false; + if (Math.abs(relHitY) > halfH) return false; + + return true; + } + @Override public boolean isIncomingProjectile(EntityEnergyProjectile projectile) { - // Don't block during charging phase - if (isCharging()) return false; - if (projectile.getOwnerEntityId() == this.ownerEntityId) return false; - - // Faction check + // Faction check: don't block same-faction NPC projectiles Entity owner = getOwnerEntity(); Entity projOwner = projectile.getOwnerEntity(); if (owner instanceof EntityNPCInterface && projOwner instanceof EntityNPCInterface) { @@ -223,38 +320,29 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { } } - // Check if projectile is within panel bounds - float halfW = panelData.panelWidth * 0.5f; - float halfH = panelData.panelHeight * 0.5f; - float panelThickness = 0.5f; // Collision thickness + double prevX = projectile.posX - projectile.motionX; + double prevY = projectile.posY - projectile.motionY; + double prevZ = projectile.posZ - projectile.motionZ; - // Transform projectile position to panel-local space - float yawRad = (float) Math.toRadians(panelYaw); - double relX = projectile.posX - this.posX; - double relY = projectile.posY - this.posY; - double relZ = projectile.posZ - this.posZ; + return isIncomingRay( + projectile.posX, projectile.posY, projectile.posZ, + prevX, prevY, prevZ, + projectile.getOwnerEntityId()); + } - // Rotate into panel space (panel faces along -sin(yaw), cos(yaw)) - double cos = Math.cos(yawRad); - double sin = Math.sin(yawRad); - double localForward = relX * (-sin) + relZ * cos; // Distance along panel normal - double localRight = relX * cos + relZ * sin; // Distance along panel width + @Override + public boolean isIncomingGenericProjectile( + double posX, double posY, double posZ, + double motionX, double motionY, double motionZ, + double prevPosX, double prevPosY, double prevPosZ, + int ownerEntityId) + { + return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId); + } - // Check bounds - if (Math.abs(localForward) > panelThickness) return false; - if (Math.abs(localRight) > halfW) return false; - if (Math.abs(relY) > halfH) return false; - - // Check incoming direction via dot product with panel normal - double normalX = -Math.sin(yawRad); - double normalZ = Math.cos(yawRad); - double dot = projectile.motionX * normalX + projectile.motionZ * normalZ; - - // Determine which side of the panel the projectile is on - if (localForward > 0 && dot >= 0) return false; // Moving away from front side - if (localForward < 0 && dot <= 0) return false; // Moving away from back side - - return true; + @Override + public float getMaxExtent() { + return Math.max(panelData.panelWidth, panelData.panelHeight) * 0.5f + 1.0f; } // ==================== CHARGING ==================== @@ -277,21 +365,26 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { setCharging(false); } - // ==================== KNOCKBACK ==================== + // ==================== KNOCKBACK (DBO-style motion prediction) ==================== /** - * Push entities away from the panel surface. + * DBO-inspired planar knockback: + * - Predict future position from current motion + * - Detect crossing or proximity to panel surface + * - Apply counter-force via full motion replacement + * - No separate phases — single unified algorithm */ @Override @SuppressWarnings("unchecked") protected void knockbackEntities() { float halfW = panelData.panelWidth * 0.5f; float halfH = panelData.panelHeight * 0.5f; - float margin = 1.0f; + float searchExtension = 5.0f; + float strength = barrierData.knockbackStrength; AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( - posX - halfW - margin, posY - halfH - margin, posZ - halfW - margin, - posX + halfW + margin, posY + halfH + margin, posZ + halfW + margin + posX - halfW - searchExtension, posY - halfH - searchExtension, posZ - halfW - searchExtension, + posX + halfW + searchExtension, posY + halfH + searchExtension, posZ + halfW + searchExtension ); List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, searchBox); @@ -304,44 +397,60 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { if (!isKnockbackTarget(ent)) continue; if (isAllyOfOwner(ent)) continue; - // Transform entity position to panel-local space + // Current position relative to panel center double dx = ent.posX - posX; double dy = (ent.posY + ent.height * 0.5) - posY; double dz = ent.posZ - posZ; - // Check proximity to panel surface + // Panel-local coordinates float localForward = (float) (dx * normalX + dz * normalZ); float localRight = (float) (dx * (-normalZ) + dz * normalX); - if (Math.abs(localForward) < margin && Math.abs(localRight) < halfW + margin && Math.abs(dy) < halfH + margin) { - // Push entity away from the panel face - float pushDir = localForward >= 0 ? 1.0f : -1.0f; - double pushStrength = barrierData.knockbackStrength * 0.5; - ent.addVelocity( - normalX * pushDir * pushStrength, - 0.1, - normalZ * pushDir * pushStrength - ); + // Skip if outside panel bounds (with margin) + if (Math.abs(localRight) > halfW + searchExtension) continue; + if (Math.abs(dy) > halfH + searchExtension) continue; + if (Math.abs(localForward) > searchExtension) continue; + + // Skip if outside panel width/height bounds (no wall to push against) + if (Math.abs(localRight) > halfW + 1.0f) continue; + if (Math.abs(dy) > halfH + 1.0f) continue; + + // Predicted future position + double futDx = (ent.posX + ent.motionX) - posX; + double futDz = (ent.posZ + ent.motionZ) - posZ; + float futLocalForward = (float) (futDx * normalX + futDz * normalZ); + + // Normal velocity component (positive = moving in normal direction) + double normalVel = ent.motionX * normalX + ent.motionZ * normalZ; + + // Determine which side the entity is on + float side = localForward >= 0 ? 1.0f : -1.0f; + boolean movingTowardPanel = (localForward >= 0 && normalVel < 0) || (localForward < 0 && normalVel > 0); + + // Check if predicted position would cross the plane (sign change) + boolean wouldCross = (localForward > 0 && futLocalForward <= 0) || (localForward < 0 && futLocalForward >= 0); + + if (wouldCross && Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH) { + // Hard block: full motion replacement to prevent crossing + // Push back along normal, proportional to how far past the surface they'd go + double overshoot = Math.abs(futLocalForward); + double pushForce = (overshoot + 0.05) * strength * 0.1; + ent.motionX = normalX * side * pushForce; + ent.motionZ = normalZ * side * pushForce; + ent.velocityChanged = true; + } else if (Math.abs(localForward) < 1.0 && movingTowardPanel + && Math.abs(localRight) <= halfW && Math.abs(dy) <= halfH) { + // Soft push: entity is close and approaching, apply proportional force + double proximity = 1.0 - Math.abs(localForward); + double pushForce = proximity * strength * 0.1; + ent.motionX = normalX * side * pushForce; + ent.motionZ = normalZ * side * pushForce; ent.velocityChanged = true; } } } - // ==================== BOUNDING BOX ==================== - - @Override - public AxisAlignedBB getBoundingBox() { - if (barrierData.meleeEnabled) { - float halfW = panelData.panelWidth * 0.5f; - float halfH = panelData.panelHeight * 0.5f; - float extent = Math.max(halfW, 0.5f); - return AxisAlignedBB.getBoundingBox( - posX - extent, posY - halfH, posZ - extent, - posX + extent, posY + halfH, posZ + extent - ); - } - return null; - } + // ==================== RENDER ==================== @Override @SideOnly(Side.CLIENT) diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 8e8b62780..5b741cc0b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -318,17 +318,18 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { * @return true if projectile was absorbed (caller should stop processing) */ protected boolean checkBarrierCollision() { - // Search range must cover the largest possible barrier (dome radius up to MAX_ENTITY_RADIUS) - double searchRange = MAX_ENTITY_RADIUS + 5; - AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( - posX - searchRange, posY - searchRange, posZ - searchRange, - posX + searchRange, posY + searchRange, posZ + searchRange - ); - - @SuppressWarnings("unchecked") - List barriers = worldObj.getEntitiesWithinAABB(EntityEnergyBarrier.class, searchBox); + List barriers = EntityEnergyBarrier.getActiveBarriers(worldObj); for (EntityEnergyBarrier barrier : barriers) { if (barrier.isDead) continue; + + // Quick distance pre-filter + double dx = barrier.posX - this.posX; + double dy = barrier.posY - this.posY; + double dz = barrier.posZ - this.posZ; + double distSq = dx * dx + dy * dy + dz * dz; + double maxRange = barrier.getMaxExtent() + 5.0; + if (distSq > maxRange * maxRange) continue; + if (barrier.isIncomingProjectile(this)) { float damage = getModifiedDamage(); if (barrier.onProjectileHit(this, damage)) { diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index ca2be8968..5cdd71bff 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -430,6 +430,7 @@ public class CustomNpcs { } kamkeel.npcs.entity.EntityEnergyProjectile.clearAllProjectiles(); + kamkeel.npcs.entity.EntityEnergyBarrier.clearAllBarriers(); if (FMLCommonHandler.instance().getSide().isClient()) clientJaninoCompiler = null; diff --git a/src/main/java/noppes/npcs/ServerEventsHandler.java b/src/main/java/noppes/npcs/ServerEventsHandler.java index b6671b72a..fb2ea3d13 100644 --- a/src/main/java/noppes/npcs/ServerEventsHandler.java +++ b/src/main/java/noppes/npcs/ServerEventsHandler.java @@ -30,6 +30,7 @@ import net.minecraft.util.MathHelper; import net.minecraft.util.StatCollector; import net.minecraft.village.MerchantRecipeList; import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import kamkeel.npcs.entity.EntityEnergyBarrier; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; import net.minecraftforge.event.entity.living.LivingEvent; @@ -151,6 +152,52 @@ public class ServerEventsHandler { } } + @SubscribeEvent + public void barrierDamageProtection(LivingAttackEvent event) { + if (event.entityLiving == null || event.entityLiving.worldObj == null || event.entityLiving.worldObj.isRemote) + return; + + EntityEnergyBarrier barrier = EntityEnergyBarrier.getProtectingBarrier(event.entityLiving); + if (barrier != null && barrier.isDamageFromOutside(event.source)) { + // Redirect the damage to the barrier instead of preventing it + barrier.absorbDamage(event.ammount); + event.setCanceled(true); + } + } + + @SubscribeEvent + public void domeItemBlacklist(PlayerInteractEvent event) { + if (event.action == PlayerInteractEvent.Action.LEFT_CLICK_BLOCK) return; + EntityPlayer player = event.entityPlayer; + if (player == null || player.worldObj == null || player.worldObj.isRemote) return; + + ItemStack held = player.getHeldItem(); + if (held == null) return; + + String itemId = net.minecraft.item.Item.itemRegistry.getNameForObject(held.getItem()); + if (itemId == null) return; + + String[] blacklist = noppes.npcs.config.ConfigEnergy.DomeItemBlacklist; + if (blacklist == null) return; + + boolean blacklisted = false; + for (String banned : blacklist) { + if (banned.equals(itemId)) { + blacklisted = true; + break; + } + } + if (!blacklisted) return; + + List barriers = EntityEnergyBarrier.getActiveBarriers(player.worldObj); + for (EntityEnergyBarrier barrier : barriers) { + if (barrier instanceof kamkeel.npcs.entity.EntityEnergyDome && barrier.isEntityInside(player)) { + event.setCanceled(true); + return; + } + } + } + @SubscribeEvent public void partyDamagedEvent(LivingAttackEvent event) { if (!(event.source != null && event.source.getEntity() instanceof EntityPlayer) || !(event.entityLiving instanceof EntityPlayer) || FMLCommonHandler.instance().getEffectiveSide().isClient()) diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index 1c16de390..b97df1159 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -5,6 +5,7 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.gameevent.InputEvent; import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; +import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.client.renderer.lightning.LightningBolt; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; @@ -203,6 +204,40 @@ public class ClientTickHandler { } } + /** + * Enforce ability lock motion constraints right before entity update. + * The Phase.END zeroing in onClientTick can be overwritten by other mods' + * ClientTickEvent Phase.START handlers (e.g. flight mods re-applying gravity). + * PlayerTickEvent fires after all ClientTickEvent handlers but before + * player.onUpdate(), making this the definitive point to enforce motion locks. + */ + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onPlayerTick(TickEvent.PlayerTickEvent event) { + if (event.phase != Phase.START || event.side != Side.CLIENT) + return; + + Minecraft mc = Minecraft.getMinecraft(); + if (mc.thePlayer == null || mc.currentScreen != null) + return; + + if (!ClientAbilityState.shouldSuppressMovementInput()) + return; + + if (!ClientAbilityState.hasAbilityMovement) { + mc.thePlayer.motionX = 0; + mc.thePlayer.motionZ = 0; + } + + if ((ClientAbilityState.movementLocked || ClientAbilityState.positionLocked) + && !ClientAbilityState.hasAbilityMovement) { + if (ClientAbilityState.wasFlyingAtLock) { + mc.thePlayer.motionY = 0; + } else { + mc.thePlayer.motionY = Math.min(mc.thePlayer.motionY, 0); + } + } + } + @SubscribeEvent public void onMouse(InputEvent.MouseInputEvent event) { if (Mouse.getEventButton() == -1 && Mouse.getEventDWheel() == 0) diff --git a/src/main/java/noppes/npcs/config/ConfigEnergy.java b/src/main/java/noppes/npcs/config/ConfigEnergy.java new file mode 100644 index 000000000..f3b36a948 --- /dev/null +++ b/src/main/java/noppes/npcs/config/ConfigEnergy.java @@ -0,0 +1,37 @@ +package noppes.npcs.config; + +import cpw.mods.fml.common.FMLLog; +import net.minecraftforge.common.config.Configuration; +import org.apache.logging.log4j.Level; + +import java.io.File; + +public class ConfigEnergy { + public static Configuration config; + + public final static String DOMES = "Domes"; + + public static String[] DomeItemBlacklist; + + public static void init(File configFile) { + config = new Configuration(configFile); + + try { + config.load(); + + DomeItemBlacklist = config.getStringList( + "Item Blacklist", DOMES, + new String[]{"minecraft:ender_pearl"}, + "Items that cannot be used while inside an Energy Dome.\n" + + "Format: modid:itemname (e.g., minecraft:ender_pearl)" + ); + + } catch (Exception e) { + FMLLog.log(Level.ERROR, e, "CustomNPC+ has had a problem loading its energy configuration"); + } finally { + if (config.hasChanged()) { + config.save(); + } + } + } +} diff --git a/src/main/java/noppes/npcs/config/LoadConfiguration.java b/src/main/java/noppes/npcs/config/LoadConfiguration.java index dfac43d90..0fc46e3e3 100644 --- a/src/main/java/noppes/npcs/config/LoadConfiguration.java +++ b/src/main/java/noppes/npcs/config/LoadConfiguration.java @@ -10,6 +10,7 @@ public class LoadConfiguration { public static File clientConfigFile; public static File experimentalConfigFile; public static File marketConfigFile; + public static File energyConfigFile; public static void init(String configpath) { mainConfigFile = new File(configpath + "main.cfg"); @@ -19,6 +20,7 @@ public class LoadConfiguration { debugConfigFile = new File(configpath + "debug.cfg"); experimentalConfigFile = new File(configpath + "experimental.cfg"); marketConfigFile = new File(configpath + "market.cfg"); + energyConfigFile = new File(configpath + "energy.cfg"); ConfigMain.init(mainConfigFile); ConfigItem.init(itemConfigFile); @@ -27,5 +29,6 @@ public class LoadConfiguration { ConfigDebug.init(debugConfigFile); ConfigExperimental.init(experimentalConfigFile); ConfigMarket.init(marketConfigFile); + ConfigEnergy.init(energyConfigFile); } } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index c2d575224..55e94b72d 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2043,6 +2043,13 @@ ability.meleeDamageMultiplier=Melee Multiplier ability.followCaster=Follow Caster ability.hover.followCaster=Dome stays centered on the caster as they move +ability.section.jail=Jail & Protection +ability.jailEnabled=Jail Mode +ability.hover.jailEnabled=Entities inside stay inside, entities outside stay outside +ability.jailThreshold=Jail Threshold +ability.damageProtection=Damage Protection +ability.hover.damageProtection=Outside attacks on entities inside are redirected to the dome + ability.variant.small=Small ability.variant.large=Large ability.variant.static=Static