Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
bigguy345 committed 2026-03-02 00:26:51 +02:00
commit 29f0282fe4
43 files changed
+767 -199

No files matched your search

+1 -1
Submodule src/api updated: b28a7a5c4d...f969b9f540.
@@ -37,6 +37,15 @@ public class DBCAddon {
public void doDBCDamage(EntityNPCInterface npc, float attackStrength, Entity receiver) {
}
/**
* Returns the pre-calculated DBC attack damage of the current attacker,
* or the vanilla damage if DBC damage was not calculated.
* Used by Counter (PERCENT mode) to base counter damage on the full DBC attack, not vanilla base.
*/
public float getAttackerDBCDamage(float vanillaDamage) {
return vanillaDamage;
}
public boolean isKO(EntityNPCInterface npc, EntityPlayer player) {
return false;
}
@@ -395,7 +395,8 @@ public class AbilityController implements IAbilityHandler {
// Clean up online players' unlocked ability lists
if (PlayerDataController.Instance != null) {
for (PlayerData pData : PlayerDataController.Instance.getAllPlayerData()) {
List<PlayerData> snapshot = new ArrayList<>(PlayerDataController.Instance.getAllPlayerData());
for (PlayerData pData : snapshot) {
if (pData.abilityData != null) {
boolean changed = false;
if (uuid != null && !uuid.isEmpty() && pData.abilityData.hasUnlockedAbility(uuid)) {
@@ -437,7 +438,7 @@ public class AbilityController implements IAbilityHandler {
return customAbilities;
}
public void setCustomAbilities(Map<String, Ability> synced) {
public synchronized void setCustomAbilities(Map<String, Ability> synced) {
customAbilities.clear();
customAbilitiesById.clear();
customAbilities.putAll(synced);
@@ -613,7 +614,8 @@ public class AbilityController implements IAbilityHandler {
// Clean up online players' unlocked ability lists (chain keys use "chain:" prefix)
if (PlayerDataController.Instance != null) {
for (PlayerData pData : PlayerDataController.Instance.getAllPlayerData()) {
List<PlayerData> snapshot = new ArrayList<>(PlayerDataController.Instance.getAllPlayerData());
for (PlayerData pData : snapshot) {
if (pData.abilityData != null) {
String chainKey = "chain:" + (uuid != null && !uuid.isEmpty() ? uuid : name);
if (pData.abilityData.hasUnlockedAbility(chainKey)) {
@@ -722,7 +724,7 @@ public class AbilityController implements IAbilityHandler {
return chainedAbilityRevision;
}
public void setChainedAbilities(Map<String, ChainedAbility> synced) {
public synchronized void setChainedAbilities(Map<String, ChainedAbility> synced) {
chainedAbilities.clear();
chainedAbilitiesById.clear();
chainedAbilities.putAll(synced);
@@ -1531,7 +1531,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
dazedAnimationId = nbt.hasKey("dazedAnimationId") ? nbt.getInteger("dazedAnimationId") : -1;
windUpAnimationName = nbt.getString("windUpAnimationName");
activeAnimationName = nbt.getString("activeAnimationName");
dazedAnimationName = nbt.getString("dazedAnimationName");
dazedAnimationName = nbt.hasKey("dazedAnimationName") ? nbt.getString("dazedAnimationName") : "Ability_Generic_Dazed";
showTelegraph = nbt.hasKey("showTelegraph") ? nbt.getBoolean("showTelegraph") : true;
try {
telegraphType = TelegraphType.valueOf(nbt.getString("telegraphType"));
@@ -2356,7 +2356,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
}
// ═══════════════════════════════════════════════════════════════════
// SHIT FROM THE ASS
// WINDUP DURATION CALCULATION
// ═══════════════════════════════════════════════════════════════════
private int calculateWindupFromAnimation() {
@@ -308,6 +308,7 @@ public class AbilityAction {
}
// Inline ability
if (AbilityController.Instance == null) return null;
Ability ability = AbilityController.Instance.fromNBT(nbt);
if (ability == null) return null;
return inline(ability);
@@ -126,7 +126,7 @@ public class CustomAbility extends Ability implements ICustomAbility {
@Override
public void readTypeNBT(NBTTagCompound nbt) {
this.durationTicks = nbt.getInteger("durationTicks");
this.durationTicks = Math.max(0, nbt.getInteger("durationTicks"));
}
// ==================== GUI ====================
@@ -76,7 +76,7 @@ public class ConditionHitCount extends AbilityCondition {
@Override
public void readTypeNBT(NBTTagCompound nbt) {
requiredHits = nbt.getInteger("requiredHits");
withinTicks = nbt.getInteger("withinTicks");
requiredHits = Math.max(0, nbt.getInteger("requiredHits"));
withinTicks = Math.max(0, nbt.getInteger("withinTicks"));
}
}
@@ -174,7 +174,7 @@ public class ConditionItem extends AbilityCondition {
itemName = nbt.getString("itemName");
usageType = UsageType.fromOrdinal(nbt.getInteger("usageType"));
armorSlot = ArmorSlot.fromOrdinal(nbt.getInteger("armorSlot"));
requiredCount = nbt.getInteger("requiredCount");
requiredCount = Math.max(1, nbt.getInteger("requiredCount"));
}
@@ -30,7 +30,9 @@ public class ConditionQuestCompleted extends AbilityCondition {
if (questId < 0) return false;
EntityPlayer player = (EntityPlayer) entity;
return PlayerData.get(player).questData.hasFinishedQuest(questId);
PlayerData data = PlayerData.get(player);
if (data == null || data.questData == null) return false;
return data.questData.hasFinishedQuest(questId);
}
@SideOnly(Side.CLIENT)
@@ -289,9 +289,15 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
*/
public boolean checkConditions(EntityLivingBase caster, EntityLivingBase target) {
for (AbilityCondition c : conditions) {
if (!c.check(caster, target)) {
return false;
if (!c.getUserType().allowsNpc()) continue;
Boolean extendedCondition = AbilityController.Instance.fireCheckConditions(c, caster, target);
if (extendedCondition != null) {
if (!extendedCondition) return false;
continue;
}
if (!c.check(caster, target)) return false;
}
return true;
}
@@ -301,10 +307,16 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
*/
public boolean checkConditionsForPlayer(EntityLivingBase caster) {
for (AbilityCondition c : conditions) {
if (!c.getUserType().allowsPlayer()) continue;
if (c.requiresTarget()) continue;
if (!c.check(caster, null)) {
return false;
Boolean extendedCondition = AbilityController.Instance.fireCheckConditionsForPlayer(c, caster);
if (extendedCondition != null) {
if (!extendedCondition) return false;
continue;
}
if (!c.check(caster, null)) return false;
}
return true;
}
@@ -9,6 +9,7 @@ public class EnergyAnchorData implements IEnergyAnchorData {
public float anchorOffsetX = 0;
public float anchorOffsetY = 0;
public float anchorOffsetZ = 0;
public boolean launchFromAnchor = false;
public EnergyAnchorData() {
}
@@ -83,11 +84,22 @@ public class EnergyAnchorData implements IEnergyAnchorData {
this.anchorOffsetZ = anchorOffsetZ;
}
@Override
public boolean getLaunchFromAnchor() {
return launchFromAnchor;
}
@Override
public void setLaunchFromAnchor(boolean launchFromAnchor) {
this.launchFromAnchor = launchFromAnchor;
}
public void writeNBT(NBTTagCompound nbt) {
nbt.setInteger("anchorPoint", anchorPoint.ordinal());
nbt.setFloat("anchorOffsetX", anchorOffsetX);
nbt.setFloat("anchorOffsetY", anchorOffsetY);
nbt.setFloat("anchorOffsetZ", anchorOffsetZ);
nbt.setBoolean("launchFromAnchor", launchFromAnchor);
}
public void readNBT(NBTTagCompound nbt) {
@@ -95,9 +107,12 @@ public class EnergyAnchorData implements IEnergyAnchorData {
this.anchorOffsetX = nbt.getFloat("anchorOffsetX");
this.anchorOffsetY = nbt.getFloat("anchorOffsetY");
this.anchorOffsetZ = nbt.getFloat("anchorOffsetZ");
this.launchFromAnchor = nbt.getBoolean("launchFromAnchor");
}
public EnergyAnchorData copy() {
return new EnergyAnchorData(anchorPoint, anchorOffsetX, anchorOffsetY, anchorOffsetZ);
EnergyAnchorData copy = new EnergyAnchorData(anchorPoint, anchorOffsetX, anchorOffsetY, anchorOffsetZ);
copy.launchFromAnchor = this.launchFromAnchor;
return copy;
}
}
@@ -9,10 +9,7 @@ import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import noppes.npcs.NpcDamageSource;
import noppes.npcs.controllers.data.Animation;
import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.api.ability.type.IAbilityCounter;
import noppes.npcs.client.gui.builder.FieldDef;
@@ -66,27 +63,24 @@ public class AbilityCounter extends AbilityDefend implements IAbilityCounter {
@Override
protected float performDefend(EntityLivingBase attacker, float amount) {
// For PERCENT mode with DBC, use the full DBC-calculated damage, not vanilla base
float incomingDamage = amount;
if (DBCAddon.IsAvailable() && attacker instanceof EntityPlayer) {
incomingDamage = DBCAddon.instance.getAttackerDBCDamage(amount);
}
// Calculate counter damage
float damage;
if (counterType == CounterType.FLAT) {
damage = counterValue;
} else {
damage = amount * (counterValue / 100.0f);
damage = incomingDamage * (counterValue / 100.0f);
}
// Hit back
if (caster != null && attacker.isEntityAlive()) {
if (caster instanceof EntityNPCInterface && DBCAddon.instance.canDBCAttack((EntityNPCInterface) caster, damage, attacker)) {
// NPC with DBC: dummy hit for events/knockback, then apply DBC damage with NPC's stats
attacker.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), 0.001f);
DBCAddon.instance.doDBCDamage((EntityNPCInterface) caster, damage, attacker);
} else {
// Player or non-DBC NPC: normal damage
DamageSource counterSource = caster instanceof EntityPlayer
? DamageSource.causePlayerDamage((EntityPlayer) caster)
: DamageSource.causeMobDamage(caster);
attacker.attackEntityFrom(counterSource, damage);
}
// Counter-attack through the standard ability damage pipeline.
// DBCAbilityExtender handles DBC stat routing (ignore flags, scaling, etc.) automatically.
if (caster != null && attacker.isEntityAlive() && damage > 0) {
applyAbilityDamage(caster, attacker, damage, 0);
}
return 0;
@@ -41,6 +41,10 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend {
protected transient float lastDamageTaken;
protected transient Animation pendingDefendAnimation;
protected transient int defendAnimEndTick = -1;
/** Prevents double-counting hits when multiple damage paths call onDefend for the same hit in a single tick. */
protected transient long lastDefendTick = -1;
/** Defers signalCompletion to the next tick so all damage paths for the last hit still see isDefending()=true. */
protected transient boolean pendingCompletion;
// ═══════════════════════════════════════════════════════════════════
// CONSTRUCTOR DEFAULTS
@@ -69,7 +73,7 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend {
@Override
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
if (tick >= durationTicks) {
if (pendingCompletion || tick >= durationTicks) {
signalCompletion();
}
}
@@ -83,6 +87,8 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend {
lastDamageTaken = 0.0f;
pendingDefendAnimation = null;
defendAnimEndTick = -1;
lastDefendTick = -1;
pendingCompletion = false;
}
// ═══════════════════════════════════════════════════════════════════
@@ -99,30 +105,47 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend {
* @return The modified damage to apply (unchanged if not physical melee)
*/
public final float onDefend(EntityLivingBase attacker, DamageSource source, float amount) {
if (!isDefending())
if (!isDefending()) {
return amount;
if (attacker == null || source == null)
}
if (attacker == null || source == null) {
return amount;
// Only react to physical melee damage
if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion() || source.isProjectile())
return amount;
lastAttacker = attacker;
lastDamageTaken = amount;
hitCount++;
float result = performDefend(attacker, amount);
// Queue defend reaction animation (played on next tick by AbstractDataAbilities)
pendingDefendAnimation = getDefendAnimation();
// Auto-complete after max hits
if (maxHitAmount > 0 && hitCount >= maxHitAmount) {
signalCompletion();
}
return result;
// Only react to physical melee damage
if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion() || source.isProjectile()) {
return amount;
}
// Deduplicate per tick: when multiple damage paths (e.g. vanilla + DBC) call onDefend
// for the same hit, only count it once for hitCount / signalCompletion / animation.
long currentTick = (caster != null && caster.worldObj != null)
? caster.worldObj.getTotalWorldTime() : -1;
boolean firstCallThisTick = (currentTick < 0 || currentTick != lastDefendTick);
// If max hits reached on a previous tick and this is a NEW hit, reject it
if (pendingCompletion && firstCallThisTick) {
return amount;
}
if (firstCallThisTick) {
lastDefendTick = currentTick;
lastAttacker = attacker;
lastDamageTaken = amount;
hitCount++;
// Queue defend reaction animation (played on next tick by AbstractDataAbilities)
pendingDefendAnimation = getDefendAnimation();
// Defer completion to next tick so all damage paths in this tick
// still see isDefending()=true and apply guard reduction.
if (maxHitAmount > 0 && hitCount >= maxHitAmount) {
pendingCompletion = true;
}
}
// Always calculate the correct reduction for the caller's damage amount
return performDefend(attacker, amount);
}
/**
@@ -787,6 +787,14 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
projectiles[0].anchor.anchorOffsetZ = z;
}
public boolean getLaunchFromAnchor() {
return projectiles[0].anchor.launchFromAnchor;
}
public void setLaunchFromAnchor(boolean launchFromAnchor) {
projectiles[0].anchor.launchFromAnchor = launchFromAnchor;
}
@Override
public int getAnchorPoint() {
return projectiles[0].anchor.anchorPoint.ordinal();
@@ -840,6 +848,14 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
projectiles[clampIndex(index)].anchor.anchorOffsetZ = offset;
}
public boolean getLaunchFromAnchor(int index) {
return projectiles[clampIndex(index)].anchor.launchFromAnchor;
}
public void setLaunchFromAnchor(int index, boolean launchFromAnchor) {
projectiles[clampIndex(index)].anchor.launchFromAnchor = launchFromAnchor;
}
// ==================== GUI FIELD DEFINITIONS ====================
@SideOnly(Side.CLIENT)
@@ -889,6 +905,9 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
).tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount));
defs.add(FieldDef.floatField("ability.anchor.offsetZ", () -> projectiles[idx].anchor.anchorOffsetZ, v -> projectiles[idx].anchor.anchorOffsetZ = v)
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount).min(Float.NEGATIVE_INFINITY));
defs.add(FieldDef.boolField("ability.launchFromAnchor", () -> projectiles[idx].anchor.launchFromAnchor, v -> projectiles[idx].anchor.launchFromAnchor = v)
.tab("ability.tab.visual").visibleWhen(() -> idx < projectileCount)
.hover("ability.hover.launchFromAnchor"));
// Color override (only when multiple projectiles)
defs.add(FieldDef.boolField("ability.colorOverride", () -> projectiles[idx].colorOverride, v -> projectiles[idx].colorOverride = v)
@@ -51,8 +51,7 @@ public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> implem
this.activeAnimationName = "Ability_Orb_Active";
this.defaultIconLayers = new DefaultIconLayer[]{
new DefaultIconLayer("customnpcs:textures/gui/ability/orb.png"),
new DefaultIconLayer("customnpcs:textures/gui/ability/orb_overlay.png",
new DefaultIconLayer("customnpcs:textures/gui/ability/orb.png",
() -> isOuterColorEnabled() ? getOuterColor() : getInnerColor())
};
}
@@ -809,7 +809,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
nbt.setDouble("HeadOffsetY", headOffsetY);
nbt.setDouble("HeadOffsetZ", headOffsetZ);
nbt.setBoolean("AttachedToOwner", isAnchoredMode());
nbt.setBoolean("RenderTailOrb", isAnchoredMode());
nbt.setString("BeamMode", beamMode.name());
writeChargingNBT(nbt);
nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance);
@@ -779,10 +779,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
@Override
protected void readProjectileNBT(NBTTagCompound nbt) {
this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
this.expansionSpeed = nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f;
if (Float.isNaN(expansionSpeed) || Float.isInfinite(expansionSpeed) || expansionSpeed <= 0) expansionSpeed = 2.0f;
this.maxLength = nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f;
if (Float.isNaN(maxLength) || Float.isInfinite(maxLength) || maxLength <= 0) maxLength = 32.0f;
this.expansionSpeed = sanitize(nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f, 0.1f, MAX_ENTITY_SIZE);
this.maxLength = sanitize(nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f, 1.0f, MAX_ENTITY_SIZE);
this.dirX = nbt.getDouble("DirX");
this.dirY = nbt.getDouble("DirY");
this.dirZ = nbt.getDouble("DirZ");
@@ -790,7 +790,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD
this.shape = ZoneShape.CIRCLE;
}
this.ownerEntityId = buffer.readInt();
this.radius = buffer.readFloat();
this.radius = Math.max(0.1f, Math.min(100.0f, buffer.readFloat()));
if (Float.isNaN(radius) || Float.isInfinite(radius)) radius = 5.0f;
this.durationTicks = buffer.readInt();
this.maxTicks = buffer.readInt();
this.innerColor = buffer.readInt();
@@ -171,6 +171,32 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
double prevPosX, double prevPosY, double prevPosZ,
int ownerEntityId);
/**
* Compute the outward surface normal at a given hit point on this barrier.
* Used by external systems (e.g., mixin-based projectile reflection) that cannot
* access barrier-subtype internals.
*
* @param hitX, hitY, hitZ Position near the barrier surface
* @param velX, velY, velZ Incoming velocity (used as fallback direction)
* @return double[3] normalized surface normal pointing outward from the barrier
*/
public abstract double[] getSurfaceNormal(double hitX, double hitY, double hitZ,
double velX, double velY, double velZ);
/**
* Compute a position just outside the barrier surface, along the outward normal
* from the given position. Used to snap paused/reflected projectiles outside
* the collision zone.
*
* @param px, py, pz Current projectile position
* @param velX, velY, velZ Current velocity (used as fallback for direction)
* @param bias Distance outside the surface to snap to
* @return double[3] position outside the barrier surface
*/
public abstract double[] getOutsideSurfacePoint(double px, double py, double pz,
double velX, double velY, double velZ,
float bias);
// ==================== GENERIC PROJECTILE HIT ====================
/**
@@ -209,6 +235,71 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
return true;
}
/**
* Apply damage from a generic (non-CNPC+) projectile and return full hit context.
* Same logic as onProjectileHitResolved() but for raw Entity + typeId.
*
* @param projectileEntity The entity that hit the barrier (for event context)
* @param damage Base damage to apply
* @param typeId Type identifier for damage multiplier lookup (e.g., "dbc.ki_attack")
* @return Full ProjectileHitOutcome with result, reflect data, remaining damage
*/
public ProjectileHitOutcome onGenericProjectileHitResolved(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 new ProjectileHitOutcome(
ProjectileHitResult.PASS,
currentHealth, currentHealth,
0.0f, 0.0f,
barrierData.reflect, barrierData.reflectStrengthPct,
barrierData.useHealth
);
}
finalDamage = eventDamage;
}
triggerHitFlash();
float healthBefore = currentHealth;
if (!barrierData.useHealth) {
return new ProjectileHitOutcome(
ProjectileHitResult.BLOCKED,
healthBefore, healthBefore,
0.0f, 0.0f,
barrierData.reflect, barrierData.reflectStrengthPct,
false
);
}
currentHealth -= finalDamage;
syncHealthPercent();
if (currentHealth <= 0) {
float remaining = Math.max(0.0f, finalDamage - healthBefore);
onBarrierDestroyed();
this.setDead();
return new ProjectileHitOutcome(
ProjectileHitResult.BROKEN,
healthBefore, 0.0f,
finalDamage, remaining,
barrierData.reflect, barrierData.reflectStrengthPct,
true
);
}
return new ProjectileHitOutcome(
ProjectileHitResult.BLOCKED,
healthBefore, currentHealth,
finalDamage, 0.0f,
barrierData.reflect, barrierData.reflectStrengthPct,
true
);
}
// ==================== PROJECTILE HIT ====================
/**
@@ -213,6 +213,56 @@ public class EntityEnergyDome extends EntityEnergyBarrier {
return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId);
}
@Override
public double[] getSurfaceNormal(double hitX, double hitY, double hitZ,
double velX, double velY, double velZ) {
double nx = hitX - this.posX;
double ny = hitY - this.posY;
double nz = hitZ - this.posZ;
double lenSq = nx * nx + ny * ny + nz * nz;
if (lenSq > 1.0e-8) {
double invLen = 1.0 / Math.sqrt(lenSq);
return new double[]{nx * invLen, ny * invLen, nz * invLen};
}
// Fallback: use negative velocity direction
double vLenSq = velX * velX + velY * velY + velZ * velZ;
if (vLenSq > 1.0e-8) {
double invVLen = 1.0 / Math.sqrt(vLenSq);
return new double[]{-velX * invVLen, -velY * invVLen, -velZ * invVLen};
}
return new double[]{0.0, 1.0, 0.0};
}
@Override
public double[] getOutsideSurfacePoint(double px, double py, double pz,
double velX, double velY, double velZ,
float bias) {
double nx = px - this.posX;
double ny = py - this.posY;
double nz = pz - this.posZ;
double len = Math.sqrt(nx * nx + ny * ny + nz * nz);
if (len < 1.0e-5) {
double vLen = Math.sqrt(velX * velX + velY * velY + velZ * velZ);
if (vLen > 1.0e-5) {
nx = -velX / vLen;
ny = -velY / vLen;
nz = -velZ / vLen;
} else {
nx = 0.0; ny = 1.0; nz = 0.0;
}
len = 1.0;
}
double invLen = 1.0 / len;
double target = domeRadius + bias;
return new double[]{
this.posX + nx * invLen * target,
this.posY + ny * invLen * target,
this.posZ + nz * invLen * target
};
}
@Override
public float getMaxExtent() {
return domeRadius;
@@ -362,6 +362,63 @@ public class EntityEnergyPanel extends EntityEnergyBarrier {
return isIncomingRay(posX, posY, posZ, prevPosX, prevPosY, prevPosZ, ownerEntityId);
}
@Override
public double[] getSurfaceNormal(double hitX, double hitY, double hitZ,
double velX, double velY, double velZ) {
float yawRad = (float) Math.toRadians(panelYaw);
double nx = -Math.sin(yawRad);
double nz = Math.cos(yawRad);
// Determine which side the hit is on
double relX = hitX - this.posX;
double relZ = hitZ - this.posZ;
double side = relX * nx + relZ * nz;
if (Math.abs(side) < 1.0e-5) {
// Near the plane: use incoming velocity to determine side
side = velX * nx + velZ * nz;
}
if (side < 0.0) {
nx = -nx;
nz = -nz;
}
return new double[]{nx, 0.0, nz};
}
@Override
public double[] getOutsideSurfacePoint(double px, double py, double pz,
double velX, double velY, double velZ,
float bias) {
float yawRad = (float) Math.toRadians(panelYaw);
double normalX = -Math.sin(yawRad);
double normalZ = Math.cos(yawRad);
double relX = px - this.posX;
double relZ = pz - this.posZ;
double signedDist = relX * normalX + relZ * normalZ;
double halfThickness = 0.25;
double side;
if (Math.abs(signedDist) > 0.035) {
side = signedDist >= 0 ? 1.0 : -1.0;
} else {
double vDot = velX * normalX + velZ * normalZ;
if (Math.abs(vDot) > 1.0e-5) {
side = vDot < 0 ? 1.0 : -1.0;
} else {
side = signedDist >= 0 ? 1.0 : -1.0;
}
}
double targetDist = side * (halfThickness + bias);
double delta = targetDist - signedDist;
return new double[]{
px + normalX * delta,
py,
pz + normalZ * delta
};
}
@Override
public float getMaxExtent() {
return Math.max(panelData.panelWidth, panelData.panelHeight) * 0.5f + 1.0f;
@@ -116,8 +116,6 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// ==================== HOMING PROPERTIES ====================
protected EnergyHomingData homingData = new EnergyHomingData();
protected Map<Integer, Integer> pathDelays;
// ==================== TRACKING ====================
protected double startX, startY, startZ;
protected int targetEntityId = -1;
@@ -681,47 +679,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
}
protected double[] getBarrierImpactNormal(EntityEnergyBarrier barrier, double velocityX, double velocityY, double velocityZ) {
if (barrier instanceof EntityEnergyDome) {
EntityEnergyDome dome = (EntityEnergyDome) barrier;
return normalizeVector(posX - dome.posX, posY - dome.posY, posZ - dome.posZ, velocityX, velocityY, velocityZ);
}
if (barrier instanceof EntityEnergyPanel) {
EntityEnergyPanel panel = (EntityEnergyPanel) barrier;
float yawRad = (float) Math.toRadians(panel.getPanelYaw());
double nx = -Math.sin(yawRad);
double nz = Math.cos(yawRad);
double relX = posX - panel.posX;
double relZ = posZ - panel.posZ;
double signedDist = relX * nx + relZ * nz;
double side = signedDist;
if (Math.abs(side) < 1.0e-5) {
side = velocityX * nx + velocityZ * nz;
}
if (side < 0.0) {
nx = -nx;
nz = -nz;
}
return normalizeVector(nx, 0.0, nz, velocityX, velocityY, velocityZ);
}
return normalizeVector(posX - barrier.posX, posY - barrier.posY, posZ - barrier.posZ, velocityX, velocityY, velocityZ);
}
protected double[] normalizeVector(double x, double y, double z, double fallbackX, double fallbackY, double fallbackZ) {
double lenSq = x * x + y * y + z * z;
if (lenSq > 1.0e-8) {
double invLen = 1.0 / Math.sqrt(lenSq);
return new double[]{x * invLen, y * invLen, z * invLen};
}
double fallbackLenSq = fallbackX * fallbackX + fallbackY * fallbackY + fallbackZ * fallbackZ;
if (fallbackLenSq > 1.0e-8) {
double invFallbackLen = 1.0 / Math.sqrt(fallbackLenSq);
return new double[]{-fallbackX * invFallbackLen, -fallbackY * invFallbackLen, -fallbackZ * invFallbackLen};
}
return new double[]{0.0, 1.0, 0.0};
return barrier.getSurfaceNormal(posX, posY, posZ, velocityX, velocityY, velocityZ);
}
protected void beginBarrierImpactPause(boolean destroyOnResume, EntityEnergyBarrier barrier) {
@@ -752,73 +710,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
if (barrier == null) return;
float bias = getBarrierPauseOutsideDistance();
if (barrier instanceof EntityEnergyDome) {
EntityEnergyDome dome = (EntityEnergyDome) barrier;
double nx = posX - dome.posX;
double ny = posY - dome.posY;
double nz = posZ - dome.posZ;
double len = Math.sqrt(nx * nx + ny * ny + nz * nz);
if (len < 1.0e-5) {
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
if (vLen > 1.0e-5) {
nx = -motionX / vLen;
ny = -motionY / vLen;
nz = -motionZ / vLen;
len = 1.0;
} else {
nx = 0.0;
ny = 1.0;
nz = 0.0;
len = 1.0;
}
}
double invLen = 1.0 / len;
double target = dome.getDomeRadius() + bias;
setPosition(
dome.posX + nx * invLen * target,
dome.posY + ny * invLen * target,
dome.posZ + nz * invLen * target
);
return;
}
if (barrier instanceof EntityEnergyPanel) {
EntityEnergyPanel panel = (EntityEnergyPanel) barrier;
float yawRad = (float) Math.toRadians(panel.getPanelYaw());
double normalX = -Math.sin(yawRad);
double normalZ = Math.cos(yawRad);
double relX = posX - panel.posX;
double relZ = posZ - panel.posZ;
double signedDist = relX * normalX + relZ * normalZ;
double halfThickness = 0.25;
double side;
if (Math.abs(signedDist) > 0.035) {
side = signedDist >= 0 ? 1.0 : -1.0;
} else {
// Near the plane, use incoming direction so we don't snap through and "fling".
double vDot = motionX * normalX + motionZ * normalZ;
if (Math.abs(vDot) > 1.0e-5) {
side = vDot < 0 ? 1.0 : -1.0;
} else {
side = signedDist >= 0 ? 1.0 : -1.0;
}
}
double targetDist = side * (halfThickness + bias);
double delta = targetDist - signedDist;
setPosition(posX + normalX * delta, posY, posZ + normalZ * delta);
return;
}
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
if (vLen > 1.0e-5) {
setPosition(
posX - (motionX / vLen) * bias,
posY - (motionY / vLen) * bias,
posZ - (motionZ / vLen) * bias
);
}
double[] point = barrier.getOutsideSurfacePoint(posX, posY, posZ, motionX, motionY, motionZ, bias);
setPosition(point[0], point[1], point[2]);
}
protected boolean tickBarrierImpactPause() {
@@ -1436,10 +1329,16 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
}
/**
* Only player-cast projectiles should be recentered to look-vector launch origin.
* NPC projectiles keep their anchor charge origin so casts originate from the configured anchor.
* Determines whether the projectile should snap to the look-vector launch origin on fire.
* By default, player-cast projectiles snap (launch from eye position along crosshair),
* and NPC projectiles keep their anchor charge origin.
* When {@code anchorData.launchFromAnchor} is true, snapping is disabled so the
* projectile launches from its configured anchor position instead.
*/
protected boolean shouldSnapLaunchToLookVector(EntityLivingBase owner) {
if (anchorData.launchFromAnchor) {
return false;
}
return owner instanceof EntityPlayer;
}
@@ -81,6 +81,10 @@ public final class AbilityHotbarSyncPacket extends AbstractPacket {
if (valid && data.abilityData != null) {
valid = data.abilityData.hasUnlockedAbility(slot.abilityKey);
// For chains, also check with resolved key in case key format differs
if (!valid && slot.isChainKey()) {
valid = data.abilityData.hasUnlockedAbility("chain:" + slot.getResolveKey());
}
}
if (!valid) {
@@ -51,6 +51,7 @@ public final class AbilityHotbarSavePacket extends AbstractPacket {
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
int slot = in.readInt();
NBTTagCompound compound = ByteBufUtils.readNBT(in);
if (compound == null) return;
if (slot < 0 || slot >= AbilityHotbarData.TOTAL_SLOTS) return;
@@ -73,6 +74,10 @@ public final class AbilityHotbarSavePacket extends AbstractPacket {
if (valid && data.abilityData != null) {
valid = data.abilityData.hasUnlockedAbility(slotData.abilityKey);
// For chains, also check with resolved key in case key format differs
if (!valid && slotData.isChainKey()) {
valid = data.abilityData.hasUnlockedAbility("chain:" + slotData.getResolveKey());
}
}
}
@@ -66,6 +66,7 @@ public final class CustomAbilitySavePacket extends AbstractPacket {
return;
NBTTagCompound compound = ByteBufUtils.readNBT(in);
if (compound == null) return;
Ability ability = AbilityController.Instance.fromNBT(compound);
if (ability != null) {
AbilityController.Instance.saveCustomAbility(ability);
@@ -81,7 +81,9 @@ public final class AbilityScriptPacket extends AbstractPacket {
if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT))
return;
Action requestedAction = Action.values()[in.readInt()];
int actionOrd = in.readInt();
if (actionOrd < 0 || actionOrd >= Action.values().length) return;
Action requestedAction = Action.values()[actionOrd];
String id = ByteBufUtils.readUTF8String(in);
Ability ability = AbilityController.Instance.resolveAbility(id);
@@ -81,7 +81,9 @@ public final class ChainedAbilityScriptPacket extends AbstractPacket {
if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT))
return;
Action requestedAction = Action.values()[in.readInt()];
int actionOrd = in.readInt();
if (actionOrd < 0 || actionOrd >= Action.values().length) return;
Action requestedAction = Action.values()[actionOrd];
String id = ByteBufUtils.readUTF8String(in);
ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(id);
@@ -1113,11 +1113,8 @@ public abstract class AbstractDataAbilities {
if (hasExecutingAbility) {
AbilityPhase phase = currentAbility.getPhase();
// Cannot cancel during DAZED (already interrupted)
if (phase == AbilityPhase.DAZED) return;
// Only allow cancel during active phases
if (phase != AbilityPhase.WINDUP && phase != AbilityPhase.ACTIVE
&& phase != AbilityPhase.BURST_DELAY) return;
// Only allow cancel during WINDUP phase
if (phase != AbilityPhase.WINDUP) return;
// Remove telegraph
removeTelegraph(currentAbility);
@@ -649,9 +649,11 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
// Search matches either the display name or the typeId (strip color codes for matching)
String stripped = displayName.replaceAll("\u00A7.", "");
if (search.isEmpty() || stripped.toLowerCase().contains(search) || typeId.toLowerCase().contains(search)) {
// Lime green for concurrent-capable types, gray for other built-in types
if (AbilityController.Instance.isConcurrentCapableType(typeId)) {
displayName = "\u00A7a" + displayName;
// Pink for custom ability, light blue for concurrent-capable types, gray for other built-in types
if (typeId.equals("ability.cnpc.custom")) {
displayName = "\u00A7d" + displayName;
} else if (AbilityController.Instance.isConcurrentCapableType(typeId)) {
displayName = "\u00A7b" + displayName;
} else if (AbilityController.Instance.isBuiltInType(typeId)) {
displayName = "\u00A77" + displayName;
}
@@ -661,6 +663,21 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
}
Collections.sort(list, (a, b) -> String.CASE_INSENSITIVE_ORDER.compare(
a.replaceAll("\u00A7.", ""), b.replaceAll("\u00A7.", "")));
// Pin Custom Ability to top of list
String customEntry = null;
for (String name : list) {
String tid = displayNameToTypeId.get(name);
if (tid != null && tid.equals("ability.cnpc.custom")) {
customEntry = name;
break;
}
}
if (customEntry != null) {
list.remove(customEntry);
list.add(0, customEntry);
}
return list;
}
@@ -189,7 +189,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
}
if (ability.isConcurrentCapable()) {
sw.addLabel(new GuiNpcLabel(baseId + 3,
StatCollector.translateToLocal("ability.concurrentInfo"), 5, infoY, 0x55FF55));
StatCollector.translateToLocal("ability.concurrentInfo"), 5, infoY, 0x55FFFF));
}
}
@@ -268,7 +268,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
}
if (conditions.size() < MAX_CONDITIONS) {
sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add"));;
sw.addButton(new GuiNpcButton(BTN_ADD_COND, L_LABEL_X, y, 50, 20, "gui.add"));
y += ROW_H;
}
@@ -86,7 +86,7 @@ public class SubGuiAbilityTypeSelect extends SubGuiInterface implements ICustomS
if (typeId.equals("ability.cnpc.custom")) {
displayName = "\u00A7d" + displayName;
} else if (AbilityController.Instance.isConcurrentCapableType(typeId)) {
displayName = "\u00A7a" + displayName;
displayName = "\u00A7b" + displayName;
}
allDisplayNameToTypeId.put(displayName, typeId);
}
@@ -402,7 +402,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
// Concurrent toggle (only shown if resolved ability is concurrent-capable, never for first entry)
if (i > 0 && resolvedAbility != null && resolvedAbility.isConcurrentCapable()) {
String cLabel = entry.isConcurrentEnabled() ? "\u00A7aC" : "\u00A77C";
String cLabel = entry.isConcurrentEnabled() ? "\u00A7bC" : "\u00A77C";
GuiNpcButton cBtn = new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 5, L_LABEL_X + 288, y, 20, 20, cLabel);
cBtn.setHoverText("ability.hover.concurrent");
sw.addButton(cBtn);
@@ -309,7 +309,7 @@ public class FieldDef {
}
public boolean hasRange() {
return min != Float.NEGATIVE_INFINITY || max != Float.POSITIVE_INFINITY;
return min != 0 || max != Float.POSITIVE_INFINITY;
}
public Class<? extends Enum<?>> getEnumClass() {
@@ -1,9 +1,10 @@
package noppes.npcs.controllers.data;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.api.entity.data.IHitboxData;
import noppes.npcs.config.ConfigMain;
public class HitboxData {
public class HitboxData implements IHitboxData {
private float widthScale = 1f;
private float heightScale = 1f;
@@ -96,6 +96,21 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
*/
private static final int CANCEL_WINDOW_TICKS = 10;
/**
* Position tracking for teleport detection during ability execution.
* If the player moves more than {@link #TELEPORT_THRESHOLD_SQ} in a single tick,
* the ability is interrupted as if the player changed dimensions.
*/
private transient double lastAbilityTickX, lastAbilityTickY, lastAbilityTickZ;
private transient boolean trackingAbilityPosition = false;
/**
* Squared distance threshold for teleport detection (48 blocks).
* Normal sprint speed is ~5.6 blocks/tick; this threshold is well above
* any legitimate per-tick movement including ability-driven dashes.
*/
private static final double TELEPORT_THRESHOLD_SQ = 48.0 * 48.0;
// ═══════════════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════════════════════
@@ -257,7 +272,30 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
// Tick active toggles (independent of currentAbility)
tickActiveToggles();
if (chainDelayRemaining > 0 || (currentAbility != null && currentAbility.isExecuting())) {
boolean hasActiveAbility = chainDelayRemaining > 0 || (currentAbility != null && currentAbility.isExecuting());
if (hasActiveAbility) {
// Detect large position jump (teleport) while ability is executing.
// Must run BEFORE tickCurrentAbility / applyPositionLock so the raw
// teleported position is visible before any lock snaps it back.
if (trackingAbilityPosition) {
double dx = player.posX - lastAbilityTickX;
double dy = player.posY - lastAbilityTickY;
double dz = player.posZ - lastAbilityTickZ;
if (dx * dx + dy * dy + dz * dz > TELEPORT_THRESHOLD_SQ) {
// Player was teleported — clear ability state (same as dimension change)
trackingAbilityPosition = false;
resetOnTeleport();
return;
}
}
// Update tracked position for next tick's comparison
lastAbilityTickX = player.posX;
lastAbilityTickY = player.posY;
lastAbilityTickZ = player.posZ;
trackingAbilityPosition = true;
tickCurrentAbility();
// Apply rotation and position locks after ability tick
@@ -267,6 +305,8 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
// Sync lock state to client (only sends when flags change)
syncAbilityStateIfNeeded(player);
} else {
trackingAbilityPosition = false;
// Safety: release orphaned locks if no ability is executing
if (rotationLocked || positionLocked) {
releaseRotationControl();
@@ -610,12 +650,10 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
if (!hasExecutingAbility && !isInChainDelay) return false;
// Don't allow cancel during DAZED
// Only allow cancel during WINDUP phase
if (hasExecutingAbility) {
AbilityPhase phase = currentAbility.getPhase();
if (phase == AbilityPhase.DAZED) return false;
if (phase != AbilityPhase.WINDUP && phase != AbilityPhase.ACTIVE
&& phase != AbilityPhase.BURST_DELAY) return false;
if (phase != AbilityPhase.WINDUP) return false;
}
long worldTime = getWorldTime();
@@ -698,6 +736,15 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
* Keeps active toggles (dimension change shouldn't reset ongoing effects).
*/
public void resetOnDimensionChange() {
trackingAbilityPosition = false;
onEntityReconstructed(true, false);
}
/**
* Reset ability state when a large-distance teleport is detected during execution.
* Same behavior as dimension change — silent cleanup, clear cooldowns, keep toggles.
*/
public void resetOnTeleport() {
onEntityReconstructed(true, false);
}
@@ -719,6 +766,8 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
* The caller must trigger a full ability sync afterwards (e.g. via syncToClient).
*/
public void resetOnLogin() {
trackingAbilityPosition = false;
// Silently clean up any stale executing ability
if (currentAbility != null && currentAbility.isExecuting()) {
removeTelegraph(currentAbility);
@@ -1,8 +1,9 @@
package noppes.npcs.controllers.data;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.api.entity.data.ITintData;
public class TintData {
public class TintData implements ITintData {
private boolean tintEnabled = false;
private boolean hurtTintEnabled = true;
private boolean generalTintEnabled = false;
+16 -2
View File
@@ -84,13 +84,27 @@ public class ItemStaff extends ItemNpcInterface implements IProjectileCallback {
public void onUsingTick(ItemStack stack, EntityPlayer player, int count) {
int tick = getMaxItemUseDuration(stack) - count;
int chargeTime = 20 + material.getHarvestLevel() * 8;
if (player.worldObj.isRemote) {
spawnParticle(stack, player);
// Reset charge tick on client to prevent client-side timeout
// (resetChargeTick is not synced via DataWatcher, so the client's
// chargeTick would keep climbing and falsely trigger CHARGE_TIMEOUT_GRACE)
if (tick > chargeTime && stack.stackTagCompound != null) {
Entity existing = player.worldObj
.getEntityByID(stack.stackTagCompound.getInteger("MagicProjectile"));
if (existing instanceof EntityAbilityOrb) {
EntityAbilityOrb orb = (EntityAbilityOrb) existing;
if (orb.isCharging()) {
orb.resetChargeTick();
}
}
}
return;
}
int chargeTime = 20 + material.getHarvestLevel() * 8;
// Keep a fully-charged orb alive while the player is still holding
if (tick > chargeTime && stack.stackTagCompound != null) {
Entity existing = ((WorldServer) player.worldObj)
@@ -49,5 +49,6 @@ public class ScriptEnergyOrb<T extends EntityAbilityOrb> extends ScriptEnergyPro
entity.motionY = (dy / len) * entity.getSpeed();
entity.motionZ = (dz / len) * entity.getSpeed();
}
entity.startMoving(null);
}
}
@@ -1,6 +1,7 @@
package noppes.npcs.scripted.entity;
import kamkeel.npcs.entity.EntityEnergyProjectile;
import net.minecraft.entity.EnumCreatureAttribute;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import noppes.npcs.NoppesUtilServer;
@@ -11,7 +12,9 @@ import noppes.npcs.api.entity.ICustomNpc;
import noppes.npcs.api.entity.IEnergyProjectile;
import noppes.npcs.api.entity.IEntityLivingBase;
import noppes.npcs.api.entity.IPlayer;
import noppes.npcs.api.entity.data.IHitboxData;
import noppes.npcs.api.entity.data.IModelData;
import noppes.npcs.api.entity.data.ITintData;
import noppes.npcs.api.handler.IActionManager;
import noppes.npcs.api.handler.IOverlayHandler;
import noppes.npcs.api.handler.data.IAnimationData;
@@ -421,6 +424,62 @@ public class ScriptNpc<T extends EntityNPCInterface> extends ScriptLiving<T> imp
return this.npc.advanced.killLines;
}
public boolean getOrderedLines() {
return npc.advanced.orderedLines;
}
public void setOrderedLines(boolean ordered) {
npc.advanced.orderedLines = ordered;
}
public String getIdleSound() {
return npc.advanced.idleSound;
}
public void setIdleSound(String sound) {
npc.advanced.idleSound = sound;
}
public String getAngrySound() {
return npc.advanced.angrySound;
}
public void setAngrySound(String sound) {
npc.advanced.angrySound = sound;
}
public String getHurtSound() {
return npc.advanced.hurtSound;
}
public void setHurtSound(String sound) {
npc.advanced.hurtSound = sound;
}
public String getDeathSound() {
return npc.advanced.deathSound;
}
public void setDeathSound(String sound) {
npc.advanced.deathSound = sound;
}
public String getStepSound() {
return npc.advanced.stepSound;
}
public void setStepSound(String sound) {
npc.advanced.stepSound = sound;
}
public boolean getDisablePitch() {
return npc.advanced.disablePitch;
}
public void setDisablePitch(boolean disablePitch) {
npc.advanced.disablePitch = disablePitch;
}
/**
* Kill the npc, doesnt't despawn it
*/
@@ -848,6 +907,171 @@ public class ScriptNpc<T extends EntityNPCInterface> extends ScriptLiving<T> imp
return npc.ais.tacticalRadius;
}
public int getTacticalChance() {
return npc.ais.tacticalChance;
}
public void setTacticalChance(int chance) {
npc.ais.tacticalChance = chance;
}
public boolean getCanSwim() {
return npc.ais.canSwim;
}
public void setCanSwim(boolean canSwim) {
npc.ais.canSwim = canSwim;
}
public boolean getReactsToFire() {
return npc.ais.reactsToFire;
}
public void setReactsToFire(boolean reactsToFire) {
npc.ais.reactsToFire = reactsToFire;
}
public boolean getAvoidsWater() {
return npc.ais.avoidsWater;
}
public void setAvoidsWater(boolean avoidsWater) {
npc.ais.avoidsWater = avoidsWater;
}
public boolean getAvoidsSun() {
return npc.ais.avoidsSun;
}
public void setAvoidsSun(boolean avoidsSun) {
npc.ais.avoidsSun = avoidsSun;
}
public boolean getDirectLOS() {
return npc.ais.directLOS;
}
public void setDirectLOS(boolean directLOS) {
npc.ais.directLOS = directLOS;
}
public int getLeapType() {
return npc.ais.leapType;
}
public void setLeapType(int leapType) {
npc.ais.leapType = leapType;
}
public boolean getCanSprint() {
return npc.ais.canSprint;
}
public void setCanSprint(boolean canSprint) {
npc.ais.canSprint = canSprint;
}
public boolean getStopAndInteract() {
return npc.ais.stopAndInteract;
}
public void setStopAndInteract(boolean stopAndInteract) {
npc.ais.stopAndInteract = stopAndInteract;
}
public int getDoorInteract() {
return npc.ais.doorInteract;
}
public void setDoorInteract(int doorInteract) {
npc.ais.doorInteract = doorInteract;
}
public int getWalkingRange() {
return npc.ais.walkingRange;
}
public void setWalkingRange(int range) {
npc.ais.walkingRange = range;
}
public boolean getNpcInteracting() {
return npc.ais.npcInteracting;
}
public void setNpcInteracting(boolean npcInteracting) {
npc.ais.npcInteracting = npcInteracting;
}
public boolean getMovingPause() {
return npc.ais.movingPause;
}
public void setMovingPause(boolean movingPause) {
npc.ais.movingPause = movingPause;
}
public int getMovingPattern() {
return npc.ais.movingPattern;
}
public void setMovingPattern(int pattern) {
if (pattern < 0) pattern = 0;
if (pattern > 1) pattern = 1;
npc.ais.movingPattern = pattern;
}
public int getCanFireIndirect() {
return npc.ais.canFireIndirect;
}
public void setCanFireIndirect(int canFireIndirect) {
npc.ais.canFireIndirect = canFireIndirect;
}
public int getUseRangeMelee() {
return npc.ais.useRangeMelee;
}
public void setUseRangeMelee(int useRangeMelee) {
npc.ais.useRangeMelee = useRangeMelee;
}
public int getDistanceToMelee() {
return npc.ais.distanceToMelee;
}
public void setDistanceToMelee(int distance) {
npc.ais.distanceToMelee = distance;
}
public float getBodyOffsetX() {
return npc.ais.bodyOffsetX;
}
public void setBodyOffsetX(float offsetX) {
npc.ais.bodyOffsetX = offsetX;
npc.script.clientNeedsUpdate = true;
}
public float getBodyOffsetY() {
return npc.ais.bodyOffsetY;
}
public void setBodyOffsetY(float offsetY) {
npc.ais.bodyOffsetY = offsetY;
npc.script.clientNeedsUpdate = true;
}
public float getBodyOffsetZ() {
return npc.ais.bodyOffsetZ;
}
public void setBodyOffsetZ(float offsetZ) {
npc.ais.bodyOffsetZ = offsetZ;
npc.script.clientNeedsUpdate = true;
}
public void setIgnoreCobweb(boolean ignore) {
npc.stats.ignoreCobweb = ignore;
}
@@ -1152,6 +1376,14 @@ public class ScriptNpc<T extends EntityNPCInterface> extends ScriptLiving<T> imp
return npc instanceof EntityCustomNpc ? ((EntityCustomNpc) npc).modelData : null;
}
public IHitboxData getHitboxData() {
return npc.display.hitboxData;
}
public ITintData getTintData() {
return npc.display.tintData;
}
public void setHeadScale(float x, float y, float z) {
if (npc instanceof EntityCustomNpc) {
((EntityCustomNpc) this.npc).modelData.modelScale.head.scaleX = ValueUtil.clamp(x, 0.5f, 1.5f);
@@ -1250,6 +1482,66 @@ public class ScriptNpc<T extends EntityNPCInterface> extends ScriptLiving<T> imp
return npc.stats.resistances.knockback;
}
public boolean getDamageDisabled() {
return npc.stats.resistances.disableDamage;
}
public void setDamageDisabled(boolean disabled) {
npc.stats.resistances.disableDamage = disabled;
}
public boolean getNoFallDamage() {
return npc.stats.noFallDamage;
}
public void setNoFallDamage(boolean noFallDamage) {
npc.stats.noFallDamage = noFallDamage;
}
public boolean getImmuneToFire() {
return npc.stats.immuneToFire;
}
public void setImmuneToFire(boolean immuneToFire) {
npc.stats.immuneToFire = immuneToFire;
npc.setImmuneToFire(immuneToFire);
}
public boolean getPotionImmune() {
return npc.stats.potionImmune;
}
public void setPotionImmune(boolean potionImmune) {
npc.stats.potionImmune = potionImmune;
}
public boolean getBurnInSun() {
return npc.stats.burnInSun;
}
public void setBurnInSun(boolean burnInSun) {
npc.stats.burnInSun = burnInSun;
}
public boolean getAttackInvisible() {
return npc.stats.attackInvisible;
}
public void setAttackInvisible(boolean attackInvisible) {
npc.stats.attackInvisible = attackInvisible;
}
public int getCreatureType() {
return npc.stats.creatureType.ordinal();
}
public void setCreatureType(int type) {
EnumCreatureAttribute[] values = EnumCreatureAttribute.values();
if (type >= 0 && type < values.length) {
npc.stats.creatureType = values[type];
}
}
/**
* @param type Retaliation type. 0:normal, 1:panic, 2:retreat, 3:nothing
*/
@@ -1432,6 +1724,15 @@ public class ScriptNpc<T extends EntityNPCInterface> extends ScriptLiving<T> imp
return npc.display.skinOverlayData.overlayList.get(0).getTexture();
}
public String getGlowTexture() {
return npc.display.glowTexture;
}
public void setGlowTexture(String texture) {
npc.display.glowTexture = texture;
npc.script.clientNeedsUpdate = true;
}
public IOverlayHandler getOverlays() {
return npc.display.skinOverlayData;
}
@@ -1866,6 +1866,8 @@ ability.anchorPoint=Anchor Point
ability.anchor.offsetX=Offset X
ability.anchor.offsetY=Offset Y
ability.anchor.offsetZ=Offset Z
ability.launchFromAnchor=Launch From Anchor
ability.hover.launchFromAnchor=When enabled, the projectile launches from its anchor position instead of snapping to the default eye/look-vector position.
ability.vertical=Vertical
ability.rotationSpeed=Rotation Speed
Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB