mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 20:44:08 +00:00
Merge remote-tracking branch 'origin/syntax-highlighter-unified' into syntax-highlighter-unified
This commit is contained in:
commit
f9f1cb760c
66 files changed
+1471
-610
No files matched your search
+1
-1
@@ -6,7 +6,7 @@ plugins {
|
||||
id "dts.typescript-generator"
|
||||
}
|
||||
|
||||
version = "1.11-beta9"
|
||||
version = "1.11-beta10"
|
||||
group= "kamkeel.customnpc-plus"
|
||||
archivesBaseName = "CustomNPC-Plus"
|
||||
|
||||
|
||||
+1
-1
Submodule gradle-plugins updated: f1c82c10c8...34cbba98f1.
+1
-1
Submodule src/api updated: 3c063d8bc1...52cfe77bb7.
@@ -0,0 +1,42 @@
|
||||
package kamkeel.npcs.client.command;
|
||||
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
|
||||
/**
|
||||
* Client-only command to toggle debug logging.
|
||||
* Registered via ClientCommandHandler so it runs entirely on the client
|
||||
* without sending a packet to the server.
|
||||
*/
|
||||
public class CommandCNPCDebugger extends CommandBase {
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "cnpcdebugger";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender sender) {
|
||||
return "/cnpcdebugger <type> - Toggle client debug logging for a type (e.g. energy)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredPermissionLevel() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) {
|
||||
if (args.length == 0) {
|
||||
sender.addChatMessage(new ChatComponentText("\u00A7cUsage: /cnpcdebugger <type>"));
|
||||
return;
|
||||
}
|
||||
|
||||
String type = args[0].toLowerCase();
|
||||
boolean newState = CNPCDebug.toggleClient(type);
|
||||
String stateText = newState ? "\u00A7aENABLED" : "\u00A7cDISABLED";
|
||||
sender.addChatMessage(new ChatComponentText("\u00A77[CNPC+] \u00A7eClient debug '" + type + "' " + stateText));
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
|
||||
double endY = laser.getEndY();
|
||||
double endZ = laser.getEndZ();
|
||||
float width = laser.getLaserWidth();
|
||||
float alpha = laser.getLingerAlpha();
|
||||
float alpha = 1.0f;
|
||||
|
||||
// Calculate render offset (entity position is used as reference)
|
||||
double offsetX = x - laser.posX;
|
||||
@@ -95,6 +95,25 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
|
||||
double vertY = dz * horzX - dx * horzZ;
|
||||
double vertZ = dx * horzY - dy * horzX;
|
||||
|
||||
// Rotate perpendicular vectors around beam axis (Rodrigues' rotation)
|
||||
float rotationSpeed = laser.getRotationSpeed();
|
||||
if (rotationSpeed != 0.0f) {
|
||||
double angle = (laser.ticksExisted + partialTicks) * rotationSpeed * Math.PI / 180.0;
|
||||
double cosA = Math.cos(angle);
|
||||
double sinA = Math.sin(angle);
|
||||
|
||||
double rHorzX = horzX * cosA + vertX * sinA;
|
||||
double rHorzY = horzY * cosA + vertY * sinA;
|
||||
double rHorzZ = horzZ * cosA + vertZ * sinA;
|
||||
|
||||
double rVertX = -horzX * sinA + vertX * cosA;
|
||||
double rVertY = -horzY * sinA + vertY * cosA;
|
||||
double rVertZ = -horzZ * sinA + vertZ * cosA;
|
||||
|
||||
horzX = rHorzX; horzY = rHorzY; horzZ = rHorzZ;
|
||||
vertX = rVertX; vertY = rVertY; vertZ = rVertZ;
|
||||
}
|
||||
|
||||
// Inner scale defines the core width
|
||||
float innerScale = 0.6f;
|
||||
float innerWidth = width * innerScale;
|
||||
@@ -134,7 +153,7 @@ public class RenderAbilityLaser extends RenderEnergyAbility {
|
||||
}
|
||||
|
||||
private void renderChargingOrb(EntityAbilityLaser laser, double x, double y, double z, float partialTicks) {
|
||||
float headSize = laser.getLaserWidth() * 1.5f;
|
||||
float headSize = laser.getLaserWidth() * 0.5f;
|
||||
float chargeProgress = laser.getInterpolatedChargeProgress(partialTicks);
|
||||
float size = headSize * chargeProgress;
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ public class CommandKamkeel extends CommandBase {
|
||||
registerCommand(new AuctionCommand());
|
||||
if (ConfigMain.AttributesEnabled)
|
||||
registerCommand(new AttributeCommand());
|
||||
registerCommand(new DebugCommand());
|
||||
}
|
||||
|
||||
public void registerCommand(CommandKamkeelBase command) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package kamkeel.npcs.command;
|
||||
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
|
||||
import static kamkeel.npcs.util.ColorUtil.sendResult;
|
||||
|
||||
public class DebugCommand extends CommandKamkeelBase {
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "debug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Toggle debug logging types";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "<type>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runSubCommands() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
|
||||
if (args.length == 0) {
|
||||
throw new CommandException("Usage: /kam debug <type> (e.g. energy)");
|
||||
}
|
||||
|
||||
String type = args[0].toLowerCase();
|
||||
boolean newState = CNPCDebug.toggleServer(type);
|
||||
sendResult(sender, "Server debug '" + type + "' " + (newState ? "\u00A7aENABLED" : "\u00A7cDISABLED"));
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import kamkeel.npcs.controllers.data.ability.type.AbilityVortex;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDisc;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityDome;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityLaserShot;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityLaser;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityOrb;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityShield;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper;
|
||||
@@ -143,7 +143,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
registerType("cnpc:sweeper", AbilitySweeper::new);
|
||||
registerType("cnpc:orb", AbilityOrb::new);
|
||||
registerType("cnpc:disc", AbilityDisc::new);
|
||||
registerType("cnpc:laser_shot", AbilityLaserShot::new);
|
||||
registerType("cnpc:laser_shot", AbilityLaser::new);
|
||||
registerType("cnpc:beam", AbilityBeam::new);
|
||||
// registerType("cnpc:slicer", AbilitySlicer::new);
|
||||
|
||||
@@ -1132,10 +1132,11 @@ public class AbilityController implements IAbilityHandler {
|
||||
*/
|
||||
public boolean fireOnAbilityDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
double knockbackDirX, double knockbackDirZ) {
|
||||
double knockbackDirX, double knockbackDirZ,
|
||||
float damageMultiplier) {
|
||||
for (IAbilityExtender ext : extenders) {
|
||||
if (ext.onAbilityDamage(ability, caster, target, damage, knockback, knockbackUp,
|
||||
knockbackDirX, knockbackDirZ)) {
|
||||
knockbackDirX, knockbackDirZ, damageMultiplier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
// Check for ability extenders (e.g., DBC Addon damage routing)
|
||||
boolean handled = AbilityController.Instance.fireOnAbilityDamage(
|
||||
this, caster, hitEntity, damage, knockback, knockbackUp,
|
||||
knockbackDirX, knockbackDirZ);
|
||||
knockbackDirX, knockbackDirZ, 1.0f);
|
||||
if (!handled) {
|
||||
// Default damage path
|
||||
if (caster instanceof EntityNPCInterface) {
|
||||
|
||||
@@ -26,10 +26,12 @@ public class ProjectileData {
|
||||
* If colorOverride is true, returns a copy with overridden inner/outer colors.
|
||||
*/
|
||||
public EnergyDisplayData resolveDisplay(EnergyDisplayData primary) {
|
||||
if (!colorOverride) return primary;
|
||||
// Always return a copy so entity mutations never bleed back into the ability template.
|
||||
EnergyDisplayData resolved = primary.copy();
|
||||
resolved.innerColor = this.innerColor;
|
||||
resolved.outerColor = this.outerColor;
|
||||
if (colorOverride) {
|
||||
resolved.innerColor = this.innerColor;
|
||||
resolved.outerColor = this.outerColor;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,11 +69,15 @@ public interface IAbilityExtender {
|
||||
* @param knockbackUp The vertical knockback
|
||||
* @param knockbackDirX The X component of knockback direction
|
||||
* @param knockbackDirZ The Z component of knockback direction
|
||||
* @param damageMultiplier Accumulated proportional damage multiplier (0.0-1.0).
|
||||
* 1.0 means full damage (no reduction). Extenders that recalculate
|
||||
* damage from stats should multiply by this value.
|
||||
* @return true if damage was handled (skip default), false to pass to next extender or default
|
||||
*/
|
||||
default boolean onAbilityDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
||||
float damage, float knockback, float knockbackUp,
|
||||
double knockbackDirX, double knockbackDirZ) {
|
||||
double knockbackDirX, double knockbackDirZ,
|
||||
float damageMultiplier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
targetZ = caster.posZ;
|
||||
} else if (targetingMode == TargetingMode.AOE_TARGET && target != null && !target.isDead) {
|
||||
targetX = target.posX;
|
||||
targetY = target.posY;
|
||||
targetY = findGroundLevel(caster.worldObj, target.posX, target.posY, target.posZ, 64);
|
||||
targetZ = target.posZ;
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
targetZ = caster.posZ;
|
||||
} else if (targetingMode == TargetingMode.AOE_TARGET && target != null && !target.isDead) {
|
||||
targetX = target.posX;
|
||||
targetY = target.posY;
|
||||
targetY = findGroundLevel(caster.worldObj, target.posX, target.posY, target.posZ, 64);
|
||||
targetZ = target.posZ;
|
||||
} else {
|
||||
targetX = caster.posX;
|
||||
@@ -186,11 +186,8 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
if (isAirborne) {
|
||||
double groundY = findGroundLevel(caster.worldObj, caster.posX, caster.posY, caster.posZ, 64);
|
||||
highEnoughForAirSlam = (caster.posY - groundY) >= 3.0;
|
||||
System.out.println("[Slam] AIR CHECK: posY=" + caster.posY + " groundY=" + groundY + " dist=" + (caster.posY - groundY) + " highEnough=" + highEnoughForAirSlam);
|
||||
}
|
||||
|
||||
System.out.println("[Slam] EXECUTE: onGround=" + caster.onGround + " isAirborne=" + isAirborne + " highEnough=" + highEnoughForAirSlam + " -> " + (isAirborne && highEnoughForAirSlam ? "AIR SLAM" : "GROUND SLAM"));
|
||||
|
||||
if (isAirborne && highEnoughForAirSlam) {
|
||||
// Air slam: player is already airborne (flying, falling, jumping) and high enough
|
||||
airSlam = true;
|
||||
|
||||
+31
@@ -10,6 +10,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargeTracker;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
@@ -17,6 +18,7 @@ import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
@@ -287,6 +289,15 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
chargeVisualIds[index] = id;
|
||||
chargeVisualCaster = caster;
|
||||
EnergyChargeSpawnPacket.sendToTracking(id, previewEntity, caster);
|
||||
|
||||
// Register with server-side tracker for late-join support
|
||||
EnergyChargeTracker.Instance.add(new EnergyChargeTracker.ChargeEntry(
|
||||
id,
|
||||
previewEntity.getClass().getName(),
|
||||
previewEntity.exportSpawnNBT(),
|
||||
caster.getEntityId(),
|
||||
(int) caster.worldObj.getTotalWorldTime()
|
||||
));
|
||||
}
|
||||
|
||||
protected void removeChargeVisual(EntityLivingBase caster, int index) {
|
||||
@@ -296,6 +307,7 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
String id = chargeVisualIds[index];
|
||||
if (id != null && !id.isEmpty()) {
|
||||
EnergyChargeRemovePacket.sendToTracking(id, caster);
|
||||
EnergyChargeTracker.Instance.remove(id, caster.getEntityId());
|
||||
chargeVisualIds[index] = null;
|
||||
}
|
||||
}
|
||||
@@ -305,6 +317,10 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
for (int i = 0; i < chargeVisualIds.length; i++) {
|
||||
removeChargeVisual(caster, i);
|
||||
}
|
||||
// Safety net: ensure tracker is fully cleaned for this caster
|
||||
if (!isPreview() && caster.worldObj != null && !caster.worldObj.isRemote) {
|
||||
EnergyChargeTracker.Instance.removeAllForCaster(caster.getEntityId());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LIFECYCLE ====================
|
||||
@@ -326,6 +342,21 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
spawnedCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific entity from this ability's tracking.
|
||||
* Used when a projectile is reflected — it no longer belongs to this ability,
|
||||
* so the ability should stop waiting for it and free the caster.
|
||||
*/
|
||||
public void detachEntity(Entity entity) {
|
||||
if (entities == null || entity == null) return;
|
||||
for (int i = 0; i < entities.length; i++) {
|
||||
if (entities[i] == entity) {
|
||||
entities[i] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadyForBurstCompletion(int activeTick) {
|
||||
return fireDelay <= 0 || activeTick >= fireDelay * (projectileCount - 1);
|
||||
|
||||
+23
-27
@@ -16,39 +16,39 @@ import kamkeel.npcs.entity.EntityAbilityLaser;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Vec3;
|
||||
import noppes.npcs.api.ability.type.IAbilityLaserShot;
|
||||
import noppes.npcs.api.ability.type.IAbilityLaser;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Laser Shot ability: Fast expanding thin line.
|
||||
* Travels in a straight line from origin.
|
||||
* Laser ability: Sweeping beam that follows the caster's look vector.
|
||||
* Expands from origin to maxLength, then stays active until maxLifetime expires.
|
||||
* Single-projectile only (always projectileCount=1).
|
||||
*/
|
||||
public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser> implements IAbilityLaserShot {
|
||||
public class AbilityLaser extends AbilityEnergyProjectile<EntityAbilityLaser> implements IAbilityLaser {
|
||||
|
||||
private float laserWidth = 0.3f;
|
||||
private float expansionSpeed = 3.0f;
|
||||
private int lingerTicks = 8;
|
||||
private float maxLength = 32.0f;
|
||||
|
||||
public AbilityLaserShot() {
|
||||
public AbilityLaser() {
|
||||
super(
|
||||
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f),
|
||||
new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f, HitType.PIERCE, 5),
|
||||
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 4.0f),
|
||||
new EnergyCombatData(4.0f, 0.5f, 0.05f, false, 2.0f, 0.5f, HitType.MULTI, 5),
|
||||
new EnergyHomingData(),
|
||||
new EnergyLifespanData(150.0f, 100)
|
||||
);
|
||||
this.typeId = "ability.cnpc.laser_shot";
|
||||
this.name = "Laser Shot";
|
||||
this.name = "Laser";
|
||||
this.targetingMode = TargetingMode.AGGRO_TARGET;
|
||||
this.maxRange = 35.0f;
|
||||
this.maxRange = 30.0f;
|
||||
this.minRange = 3.0f;
|
||||
this.cooldownTicks = 0;
|
||||
this.windUpTicks = 15;
|
||||
this.lockMovement = LockMode.WINDUP_AND_ACTIVE;
|
||||
this.rotationMode = RotationMode.TRACK;
|
||||
this.rotationPhase = LockMode.WINDUP;
|
||||
this.rotationPhase = LockMode.WINDUP_AND_ACTIVE;
|
||||
this.telegraphType = TelegraphType.LINE;
|
||||
this.showTelegraph = true;
|
||||
this.windUpAnimationName = "Ability_Laser_Windup";
|
||||
@@ -69,7 +69,8 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
|
||||
|
||||
@Override
|
||||
public int getMaxPreviewDuration() {
|
||||
return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 60) : 60;
|
||||
int lifetime = lifespanData.maxLifetime;
|
||||
return lifetime > 0 ? Math.min(lifetime, 60) : 60;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +102,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
|
||||
|
||||
@Override
|
||||
public float getTelegraphLength() {
|
||||
return lifespanData.maxDistance;
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,13 +120,12 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
|
||||
spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord,
|
||||
laserWidth,
|
||||
resolved, combatData, lightningData, lifespanData,
|
||||
expansionSpeed, lingerTicks);
|
||||
expansionSpeed, maxLength);
|
||||
return laser;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireEntity(EntityAbilityLaser laser, EntityLivingBase target) {
|
||||
laser.setLockVerticalDirection(true);
|
||||
laser.startMoving(target);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
|
||||
@Override
|
||||
protected void setupEntityPreview(EntityAbilityLaser laser, EntityLivingBase caster,
|
||||
EnergyDisplayData resolved, ProjectileData projData, int index) {
|
||||
laser.setupPreview(caster, laserWidth, resolved, lightningData, projData.anchor, windUpTicks, expansionSpeed, lifespanData.maxDistance);
|
||||
laser.setupPreview(caster, laserWidth, resolved, lightningData, projData.anchor, windUpTicks, expansionSpeed, maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,14 +156,14 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
|
||||
protected void writeTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("laserWidth", laserWidth);
|
||||
nbt.setFloat("expansionSpeed", expansionSpeed);
|
||||
nbt.setInteger("lingerTicks", lingerTicks);
|
||||
nbt.setFloat("maxLength", maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readTypeSpecificNBT(NBTTagCompound nbt) {
|
||||
this.laserWidth = nbt.getFloat("laserWidth");
|
||||
this.expansionSpeed = nbt.getFloat("expansionSpeed");
|
||||
this.lingerTicks = nbt.getInteger("lingerTicks");
|
||||
this.maxLength = nbt.hasKey("maxLength") ? nbt.getFloat("maxLength") : 32.0f;
|
||||
}
|
||||
|
||||
// ==================== TYPE-SPECIFIC GETTERS ====================
|
||||
@@ -184,12 +184,12 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
|
||||
this.expansionSpeed = expansionSpeed;
|
||||
}
|
||||
|
||||
public int getLingerTicks() {
|
||||
return lingerTicks;
|
||||
public float getMaxLength() {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
public void setLingerTicks(int lingerTicks) {
|
||||
this.lingerTicks = lingerTicks;
|
||||
public void setMaxLength(float maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
// ==================== TYPE-SPECIFIC GUI ====================
|
||||
@@ -207,11 +207,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
|
||||
FieldDef.floatField("ability.laserWidth", this::getLaserWidth, this::setLaserWidth).range(0.1f, 100.0f),
|
||||
FieldDef.floatField("ability.expansionSpeed", this::getExpansionSpeed, this::setExpansionSpeed).range(0.1f, 50.0f)
|
||||
));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.lingerTicks", this::getLingerTicks, this::setLingerTicks).range(1, 200),
|
||||
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).range(1, 1200)
|
||||
));
|
||||
defs.add(FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).range(1.0f, 500.0f));
|
||||
defs.add(FieldDef.floatField("ability.maxLength", this::getMaxLength, this::setMaxLength).range(1.0f, 500.0f));
|
||||
defs.add(FieldDef.section("ability.section.explosive"));
|
||||
defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"));
|
||||
defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius)
|
||||
@@ -0,0 +1,112 @@
|
||||
package kamkeel.npcs.controllers.data.energycharge;
|
||||
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Server-side tracker for active charging preview visuals.
|
||||
* Stores enough data to resend {@link EnergyChargeSpawnPacket} to late-joining players
|
||||
* who enter tracking range after the initial broadcast.
|
||||
* <p>
|
||||
* Keyed by caster entity ID so {@code PlayerEvent.StartTracking} can look up
|
||||
* active charges for a newly tracked entity.
|
||||
*/
|
||||
public class EnergyChargeTracker {
|
||||
public static final EnergyChargeTracker Instance = new EnergyChargeTracker();
|
||||
|
||||
public static class ChargeEntry {
|
||||
public final String instanceId;
|
||||
public final String entityClassName;
|
||||
public final NBTTagCompound spawnNbt;
|
||||
public final int casterEntityId;
|
||||
public final int startTick;
|
||||
|
||||
public ChargeEntry(String instanceId, String entityClassName,
|
||||
NBTTagCompound spawnNbt, int casterEntityId, int startTick) {
|
||||
this.instanceId = instanceId;
|
||||
this.entityClassName = entityClassName;
|
||||
this.spawnNbt = spawnNbt;
|
||||
this.casterEntityId = casterEntityId;
|
||||
this.startTick = startTick;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Integer, List<ChargeEntry>> activeCharges = new HashMap<>();
|
||||
|
||||
public void add(ChargeEntry entry) {
|
||||
List<ChargeEntry> list = activeCharges.get(entry.casterEntityId);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
activeCharges.put(entry.casterEntityId, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
|
||||
public void remove(String instanceId, int casterEntityId) {
|
||||
List<ChargeEntry> list = activeCharges.get(casterEntityId);
|
||||
if (list == null) return;
|
||||
Iterator<ChargeEntry> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (it.next().instanceId.equals(instanceId)) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
activeCharges.remove(casterEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllForCaster(int casterEntityId) {
|
||||
activeCharges.remove(casterEntityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all active charges for a caster to a specific player.
|
||||
* Adjusts ChargeTick in the NBT to reflect elapsed time so the
|
||||
* late-joining client starts at the correct visual progress.
|
||||
*/
|
||||
public void sendToPlayer(int casterEntityId, EntityPlayerMP player, int currentWorldTick) {
|
||||
List<ChargeEntry> entries = activeCharges.get(casterEntityId);
|
||||
if (entries == null || entries.isEmpty()) return;
|
||||
|
||||
for (ChargeEntry entry : entries) {
|
||||
int elapsed = currentWorldTick - entry.startTick;
|
||||
int chargeDuration = entry.spawnNbt.hasKey("ChargeDuration")
|
||||
? entry.spawnNbt.getInteger("ChargeDuration") : 0;
|
||||
|
||||
// Charge already complete — server remove packet should follow shortly
|
||||
if (chargeDuration > 0 && elapsed >= chargeDuration) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NBTTagCompound adjustedNbt = (NBTTagCompound) entry.spawnNbt.copy();
|
||||
if (elapsed > 0) {
|
||||
adjustedNbt.setInteger("ChargeTick", elapsed);
|
||||
}
|
||||
|
||||
PacketHandler.Instance.sendToPlayer(
|
||||
new EnergyChargeSpawnPacket(entry.instanceId, entry.entityClassName, adjustedNbt),
|
||||
player
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasCharges(int casterEntityId) {
|
||||
List<ChargeEntry> list = activeCharges.get(casterEntityId);
|
||||
return list != null && !list.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
activeCharges.clear();
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,9 @@ public class CNPCData implements IProfileData {
|
||||
if (playerData.partyUUID != null)
|
||||
return ProfileOperation.error("Cannot switch while in Party");
|
||||
|
||||
if (playerData.abilityData.isExecutingAbility())
|
||||
return ProfileOperation.error("Cannot switch while performing an Ability");
|
||||
|
||||
return ProfileOperation.success("");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package kamkeel.npcs.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -617,4 +618,31 @@ public abstract class EntityAbilityBarrier extends EntityEnergyAbility {
|
||||
this.dataWatcher.updateObject(DW_CHARGING, (byte) (charging ? 1 : 0));
|
||||
barrierData.readNBT(nbt);
|
||||
}
|
||||
|
||||
// ==================== DEBUG LOGGING ====================
|
||||
|
||||
/**
|
||||
* Debug log called every tick for barriers. Subclasses override debugLogBarrierExtra().
|
||||
*/
|
||||
protected void debugLogBarrierTick() {
|
||||
boolean isClient = worldObj.isRemote;
|
||||
if (isClient ? !CNPCDebug.isClientEnabled("energy") : !CNPCDebug.isServerEnabled("energy"))
|
||||
return;
|
||||
|
||||
String className = getClass().getSimpleName();
|
||||
String base = String.format("[%s id=%d tick=%d] pos=(%.2f,%.2f,%.2f) charging=%b health=%.1f",
|
||||
className, getEntityId(), ticksAlive,
|
||||
posX, posY, posZ, isCharging(), currentHealth);
|
||||
|
||||
String extra = debugLogBarrierExtra();
|
||||
String full = extra.isEmpty() ? base : base + " " + extra;
|
||||
CNPCDebug.log("energy", isClient, full);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook for barrier-specific debug data.
|
||||
*/
|
||||
protected String debugLogBarrierExtra() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.LogWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -61,9 +60,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
private int trailFadeTime = 20; // Ticks for trail to fully fade
|
||||
private List<Integer> trailPointAges = new ArrayList<>();
|
||||
|
||||
// Debug logging
|
||||
private static final boolean DEBUG_LOGGING = false;
|
||||
|
||||
public EntityAbilityBeam(World world) {
|
||||
super(world);
|
||||
}
|
||||
@@ -147,7 +143,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
|
||||
// Beam-specific properties from homing data
|
||||
|
||||
this.homingData = homing;
|
||||
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
|
||||
this.beamWidth = beamWidth;
|
||||
this.headSize = headSize;
|
||||
|
||||
@@ -160,10 +156,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
// Add initial trail point at origin (relative 0,0,0)
|
||||
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
|
||||
|
||||
if (DEBUG_LOGGING && !world.isRemote) {
|
||||
LogWriter.info("[Beam] Created at origin " + x + ", " + y + ", " + z + " maxDist=" + lifespan.maxDistance);
|
||||
}
|
||||
|
||||
// Calculate initial velocity toward target
|
||||
calculateInitialVelocity(owner, target, x, y, z);
|
||||
}
|
||||
@@ -222,11 +214,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
|
||||
if (isFadingMode()) trailPointAges.add(0);
|
||||
|
||||
if (DEBUG_LOGGING && !worldObj.isRemote) {
|
||||
LogWriter.info("[Beam] startFiring: origin=(" + startX + "," + startY + "," + startZ +
|
||||
") headOffset=(" + headOffsetX + "," + headOffsetY + "," + headOffsetZ +
|
||||
") motion=(" + motionX + "," + motionY + "," + motionZ + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -237,11 +224,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
headOffsetY * headOffsetY +
|
||||
headOffsetZ * headOffsetZ
|
||||
);
|
||||
boolean exceeded = distFromOrigin >= getMaxDistance();
|
||||
if (exceeded && !worldObj.isRemote && DEBUG_LOGGING) {
|
||||
LogWriter.info("[Beam] DEAD: Max distance exceeded. dist=" + distFromOrigin + " max=" + getMaxDistance());
|
||||
}
|
||||
return exceeded;
|
||||
return distFromOrigin >= getMaxDistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -312,17 +295,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
// Add trail point
|
||||
addTrailPoint();
|
||||
|
||||
// Debug log periodically
|
||||
if (DEBUG_LOGGING && ticksExisted % 20 == 0) {
|
||||
LogWriter.info("[Beam] Server tick=" + ticksExisted + " headOffset=(" +
|
||||
String.format("%.2f", headOffsetX) + "," +
|
||||
String.format("%.2f", headOffsetY) + "," +
|
||||
String.format("%.2f", headOffsetZ) + ") origin=(" +
|
||||
String.format("%.2f", startX) + "," +
|
||||
String.format("%.2f", startY) + "," +
|
||||
String.format("%.2f", startZ) + ") trail=" + getActiveTrailSize());
|
||||
}
|
||||
|
||||
// Skip collision checks on first few ticks
|
||||
if (ticksExisted > 2) {
|
||||
checkBlockCollision(headWorldX, headWorldY, headWorldZ);
|
||||
@@ -528,9 +500,27 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
Math.max(prevHeadWorldZ, headZ) + hitSize
|
||||
);
|
||||
|
||||
if (processEntitiesInHitBox(hitBox, headX, headY, headZ) && DEBUG_LOGGING) {
|
||||
LogWriter.info("[Beam] DEAD: Entity collision at tick " + ticksExisted);
|
||||
}
|
||||
processEntitiesInHitBox(hitBox, headX, headY, headZ);
|
||||
}
|
||||
|
||||
// ==================== DEBUG ====================
|
||||
|
||||
@Override
|
||||
protected String debugLogExtra() {
|
||||
double headWorldX = startX + headOffsetX;
|
||||
double headWorldY = startY + headOffsetY;
|
||||
double headWorldZ = startZ + headOffsetZ;
|
||||
return String.format("headOff=(%.2f,%.2f,%.2f) prevHead=(%.2f,%.2f,%.2f) " +
|
||||
"origin=(%.2f,%.2f,%.2f) headWorld=(%.2f,%.2f,%.2f) " +
|
||||
"trail=%d mode=%s anchored=%b beamW=%.2f headSz=%.2f " +
|
||||
"motion=(%.3f,%.3f,%.3f)",
|
||||
headOffsetX, headOffsetY, headOffsetZ,
|
||||
prevHeadOffsetX, prevHeadOffsetY, prevHeadOffsetZ,
|
||||
startX, startY, startZ,
|
||||
headWorldX, headWorldY, headWorldZ,
|
||||
getActiveTrailSize(),
|
||||
beamMode.name(), isAnchoredMode(), beamWidth, headSize,
|
||||
motionX, motionY, motionZ);
|
||||
}
|
||||
|
||||
// ==================== GETTERS FOR RENDERER ====================
|
||||
@@ -637,6 +627,50 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
return trailFadeTime;
|
||||
}
|
||||
|
||||
// ==================== REFLECTION ====================
|
||||
|
||||
@Override
|
||||
protected boolean reflectFromBarrier(EntityAbilityBarrier barrier, float reflectStrengthPct) {
|
||||
boolean reflected = super.reflectFromBarrier(barrier, reflectStrengthPct);
|
||||
if (reflected) {
|
||||
// Switch to FREE_TRAIL: detach from caster (tail-less), trail fades out.
|
||||
setBeamMode(BeamMode.FREE_TRAIL);
|
||||
|
||||
// Reset origin to the reflection point (current head position).
|
||||
startX = posX;
|
||||
startY = posY;
|
||||
startZ = posZ;
|
||||
resetHeadOffsets();
|
||||
resetTrailStorage();
|
||||
|
||||
// Disable homing — reflected beams fly straight.
|
||||
homingData.setHoming(false);
|
||||
setTargetEntityId(-1);
|
||||
}
|
||||
return reflected;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeProjectileReflectionData(NBTTagCompound nbt) {
|
||||
nbt.setByte("BeamMode", (byte) beamMode.ordinal());
|
||||
nbt.setDouble("StartX", startX);
|
||||
nbt.setDouble("StartY", startY);
|
||||
nbt.setDouble("StartZ", startZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyProjectileReflectionData(NBTTagCompound nbt) {
|
||||
int mode = nbt.getByte("BeamMode");
|
||||
setBeamMode(mode >= 0 && mode < BeamMode.values().length ? BeamMode.values()[mode] : BeamMode.FREE_TRAIL);
|
||||
startX = nbt.getDouble("StartX");
|
||||
startY = nbt.getDouble("StartY");
|
||||
startZ = nbt.getDouble("StartZ");
|
||||
resetHeadOffsets();
|
||||
resetTrailStorage();
|
||||
homingData.setHoming(false);
|
||||
setTargetEntityId(-1);
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
|
||||
@@ -64,7 +64,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
// Initialize base properties
|
||||
initProjectile(owner, target, x, y, z, 1.0f, display, combat, lightning, lifespan);
|
||||
|
||||
this.homingData = homing;
|
||||
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
|
||||
|
||||
this.boomerang = boomerang;
|
||||
this.boomerangDelay = boomerangDelay;
|
||||
@@ -330,6 +330,17 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
updateRotation();
|
||||
}
|
||||
|
||||
// ==================== DEBUG ====================
|
||||
|
||||
@Override
|
||||
protected String debugLogExtra() {
|
||||
return String.format("motion=(%.3f,%.3f,%.3f) radius=%.2f thickness=%.2f " +
|
||||
"boomerang=%b returning=%b vertical=%b ticksSinceMiss=%d",
|
||||
motionX, motionY, motionZ,
|
||||
discRadius, discThickness,
|
||||
boomerang, returning, vertical, ticksSinceMiss);
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public float getDiscRadius() {
|
||||
@@ -395,6 +406,32 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
return lastTravelYaw;
|
||||
}
|
||||
|
||||
// ==================== REFLECTION ====================
|
||||
|
||||
@Override
|
||||
protected boolean reflectFromBarrier(EntityAbilityBarrier barrier, float reflectStrengthPct) {
|
||||
boolean reflected = super.reflectFromBarrier(barrier, reflectStrengthPct);
|
||||
if (reflected) {
|
||||
// Disable boomerang on reflected discs — the original owner is no longer
|
||||
// relevant and the return-to-owner logic causes erratic flight paths.
|
||||
this.boomerang = false;
|
||||
this.returning = false;
|
||||
}
|
||||
return reflected;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeProjectileReflectionData(NBTTagCompound nbt) {
|
||||
nbt.setBoolean("Boomerang", boomerang);
|
||||
nbt.setBoolean("Returning", returning);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyProjectileReflectionData(NBTTagCompound nbt) {
|
||||
boomerang = nbt.getBoolean("Boomerang");
|
||||
returning = nbt.getBoolean("Returning");
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,10 +50,11 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
|
||||
this.stepHeight = 0.0F;
|
||||
this.ownerEntityId = owner.getEntityId();
|
||||
this.setDomeRadius(domeRadius);
|
||||
this.displayData = display;
|
||||
this.lightningData = lightning;
|
||||
this.barrierData = barrier;
|
||||
this.currentHealth = barrier.maxHealth;
|
||||
// Defensive copy: entities must never share data objects with the source ability.
|
||||
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
|
||||
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
|
||||
this.barrierData = barrier != null ? barrier.copy() : new EnergyBarrierData();
|
||||
this.currentHealth = this.barrierData.maxHealth;
|
||||
this.setPosition(x, y, z);
|
||||
}
|
||||
|
||||
@@ -121,6 +122,8 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
|
||||
if (worldObj.isRemote && barrierData.meleeEnabled && !isCharging()) {
|
||||
adjustMeleeBoundingBox();
|
||||
}
|
||||
|
||||
debugLogBarrierTick();
|
||||
}
|
||||
|
||||
// ==================== INCOMING CHECK ====================
|
||||
@@ -296,6 +299,14 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
|
||||
double surfaceX = posX + nx * pushDist;
|
||||
double surfaceY = posY + ny * pushDist - ent.height * 0.5;
|
||||
double surfaceZ = posZ + nz * pushDist;
|
||||
|
||||
// Prevent grounded entities from being teleported below the floor.
|
||||
// The spherical surface calculation can produce a Y below ground level
|
||||
// near the dome's equator, and setPosition bypasses collision detection.
|
||||
if (ent.onGround && surfaceY < ent.posY) {
|
||||
surfaceY = ent.posY;
|
||||
}
|
||||
|
||||
teleportEntity(ent, surfaceX, surfaceY, surfaceZ);
|
||||
|
||||
// Cancel radial velocity and add corrective push
|
||||
@@ -547,6 +558,14 @@ public class EntityAbilityDome extends EntityAbilityBarrier {
|
||||
return surfaceDist * surfaceDist;
|
||||
}
|
||||
|
||||
// ==================== DEBUG ====================
|
||||
|
||||
@Override
|
||||
protected String debugLogBarrierExtra() {
|
||||
return String.format("radius=%.2f targetRadius=%.2f follow=%b",
|
||||
domeRadius, targetDomeRadius, followCaster);
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public float getDomeRadius() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package kamkeel.npcs.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityEnergyProjectile;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
@@ -20,8 +21,10 @@ import noppes.npcs.entity.EntityNPCInterface;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Laser projectile - fast expanding thin line.
|
||||
* No homing, travels in a straight line from origin to max distance.
|
||||
* Laser entity - sweeping beam that follows the caster's look vector.
|
||||
* Expands from origin at expansionSpeed until reaching maxLength,
|
||||
* then stays active until maxLifetime expires. Always tracks the owner's
|
||||
* look direction so the beam can be swept mid-shot.
|
||||
* <p>
|
||||
* Design inspired by LouisXIV's energy attack system.
|
||||
*/
|
||||
@@ -30,23 +33,26 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
// Laser-specific properties
|
||||
private float laserWidth = 0.2f;
|
||||
private float expansionSpeed = 2.0f; // Blocks per tick
|
||||
private int lingerTicks = 10; // How long laser stays visible after reaching max
|
||||
private float maxLength = 32.0f; // Maximum beam reach in blocks
|
||||
|
||||
// State
|
||||
private float currentLength = 0.0f;
|
||||
private float desiredLength = 0.0f; // Intended beam reach (grows to maxLength)
|
||||
private float currentLength = 0.0f; // Actual length after block truncation (visual + collision)
|
||||
private boolean fullyExtended = false;
|
||||
private int ticksSinceFullExtension = 0;
|
||||
|
||||
// Direction (normalized)
|
||||
private double dirX, dirY, dirZ;
|
||||
|
||||
// Lock vertical direction after firing (only update yaw, keep pitch fixed)
|
||||
private boolean lockVerticalDirection = false;
|
||||
private int reflectedLockTicks = 0;
|
||||
|
||||
// End point for rendering
|
||||
private double endX, endY, endZ;
|
||||
|
||||
// Block hit deduplication — prevents event/explosion spam every tick
|
||||
private int lastBlockHitX = Integer.MIN_VALUE;
|
||||
private int lastBlockHitY = Integer.MIN_VALUE;
|
||||
private int lastBlockHitZ = Integer.MIN_VALUE;
|
||||
private int lastExplosionTick = -100;
|
||||
private static final int EXPLOSION_COOLDOWN = 10; // ticks between explosive impacts
|
||||
|
||||
public EntityAbilityLaser(World world) {
|
||||
super(world);
|
||||
}
|
||||
@@ -59,17 +65,15 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
float laserWidth,
|
||||
EnergyDisplayData display, EnergyCombatData combat,
|
||||
EnergyLightningData lightning, EnergyLifespanData lifespan,
|
||||
float expansionSpeed, int lingerTicks) {
|
||||
float expansionSpeed, float maxLength) {
|
||||
super(world);
|
||||
|
||||
// Initialize base properties (laser doesn't rotate)
|
||||
initProjectile(owner, target, x, y, z, laserWidth, display, combat, lightning, lifespan);
|
||||
this.displayData.rotationSpeed = 0.0f;
|
||||
|
||||
// Laser-specific properties
|
||||
this.laserWidth = laserWidth;
|
||||
this.expansionSpeed = expansionSpeed;
|
||||
this.lingerTicks = lingerTicks;
|
||||
this.maxLength = maxLength;
|
||||
|
||||
// Calculate direction toward target or forward
|
||||
if (target != null) {
|
||||
@@ -117,12 +121,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
|
||||
@Override
|
||||
protected void updateRotation() {
|
||||
// Laser doesn't rotate
|
||||
// Beam rotation is handled in the renderer via perpendicular vector rotation
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkMaxDistance() {
|
||||
// Laser handles distance differently - it expands to max then lingers
|
||||
// Laser handles distance via maxLength, not max distance
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -133,66 +137,57 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
return;
|
||||
}
|
||||
|
||||
// After reflection, hold trajectory briefly so the rebound is visible.
|
||||
if (reflectedLockTicks > 0) {
|
||||
reflectedLockTicks--;
|
||||
} else {
|
||||
// Track owner's anchor position and rotation each tick
|
||||
// so the laser follows the NPC's facing direction
|
||||
// Origin/direction tracking runs on BOTH sides so the client can render the beam.
|
||||
// Once reflected, never resume owner-tracking — the laser flies in a fixed direction.
|
||||
if (!reflected) {
|
||||
updateLaserOriginAndDirection();
|
||||
}
|
||||
|
||||
// Expand the intended reach — runs on both sides for visual sync
|
||||
if (!fullyExtended) {
|
||||
// Expand the laser
|
||||
currentLength += expansionSpeed;
|
||||
|
||||
if (currentLength >= getMaxDistance()) {
|
||||
currentLength = getMaxDistance();
|
||||
desiredLength += expansionSpeed;
|
||||
if (desiredLength >= maxLength) {
|
||||
desiredLength = maxLength;
|
||||
fullyExtended = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update end point
|
||||
endX = startX + dirX * currentLength;
|
||||
endY = startY + dirY * currentLength;
|
||||
endZ = startZ + dirZ * currentLength;
|
||||
// Start from full desired length, then truncate at block hits
|
||||
currentLength = desiredLength;
|
||||
|
||||
// Check for block collision along the new segment
|
||||
if (!worldObj.isRemote) {
|
||||
checkBlockCollision();
|
||||
// Re-evaluate barriers against the current beam segment before entity hits.
|
||||
// This prevents first-tick pass-through where the pre-update barrier check
|
||||
// still sees a zero-length beam.
|
||||
if (checkBarrierCollisionForCurrentSegment()) {
|
||||
return;
|
||||
}
|
||||
checkEntityCollisionAlongLine();
|
||||
// Block raytrace — truncate beam at first solid block (both sides)
|
||||
checkBlockCollision();
|
||||
|
||||
// Update end point
|
||||
updateEndPoint();
|
||||
|
||||
// Server-only: barrier collision, entity collision, death
|
||||
if (!worldObj.isRemote) {
|
||||
if (checkLaserBarrierCollision()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Laser is fully extended, count down linger time
|
||||
ticksSinceFullExtension++;
|
||||
|
||||
// Update end point during linger (origin/direction may still change)
|
||||
endX = startX + dirX * currentLength;
|
||||
endY = startY + dirY * currentLength;
|
||||
endZ = startZ + dirZ * currentLength;
|
||||
|
||||
if (ticksSinceFullExtension >= lingerTicks) {
|
||||
if (!worldObj.isRemote) {
|
||||
EventHooks.onEnergyProjectileExpired(this);
|
||||
}
|
||||
this.setDead();
|
||||
}
|
||||
checkEntityCollisionAlongLine();
|
||||
// Death handled by base class deathWorldTime (from lifespanData.maxLifetime)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update end point from current origin + direction * length.
|
||||
*/
|
||||
private void updateEndPoint() {
|
||||
endX = startX + dirX * currentLength;
|
||||
endY = startY + dirY * currentLength;
|
||||
endZ = startZ + dirZ * currentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update laser origin and direction from the owner's current look rotation.
|
||||
* Called each tick so the laser origin stays centered on look vector.
|
||||
* Called each tick so the laser follows the caster's aim (sweeping beam).
|
||||
*/
|
||||
private void updateLaserOriginAndDirection() {
|
||||
Entity owner = getOwnerEntity();
|
||||
if (owner == null || !(owner instanceof EntityLivingBase)) {
|
||||
// On client, fall back to entity tracker interpolation when owner not loaded
|
||||
if (worldObj != null && worldObj.isRemote) {
|
||||
handleClientInterpolation();
|
||||
}
|
||||
@@ -200,7 +195,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
}
|
||||
EntityLivingBase livingOwner = (EntityLivingBase) owner;
|
||||
|
||||
// Use owner look vector (head/eye aim) for direction updates so hit logic matches visuals.
|
||||
// Get owner look vector for direction
|
||||
Vec3 look = livingOwner.getLookVec();
|
||||
if (look == null) {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
@@ -212,42 +207,28 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
);
|
||||
}
|
||||
|
||||
if (lockVerticalDirection) {
|
||||
// Keep current vertical component, but align horizontal direction to current look.
|
||||
double lookHorizontalLen = Math.sqrt(look.xCoord * look.xCoord + look.zCoord * look.zCoord);
|
||||
if (lookHorizontalLen > 0.0001) {
|
||||
double currentHorizontalLen = Math.sqrt(dirX * dirX + dirZ * dirZ);
|
||||
if (currentHorizontalLen < 0.0001) {
|
||||
currentHorizontalLen = Math.sqrt(Math.max(0.0001, 1.0 - dirY * dirY));
|
||||
}
|
||||
dirX = (look.xCoord / lookHorizontalLen) * currentHorizontalLen;
|
||||
dirZ = (look.zCoord / lookHorizontalLen) * currentHorizontalLen;
|
||||
}
|
||||
} else {
|
||||
dirX = look.xCoord;
|
||||
dirY = look.yCoord;
|
||||
dirZ = look.zCoord;
|
||||
}
|
||||
// Set direction directly from look vector (no lockVerticalDirection split)
|
||||
dirX = look.xCoord;
|
||||
dirY = look.yCoord;
|
||||
dirZ = look.zCoord;
|
||||
|
||||
// Keep direction normalized for consistent expansion and collision math.
|
||||
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
|
||||
double dirLen = Math.sqrt(direction.xCoord * direction.xCoord + direction.yCoord * direction.yCoord + direction.zCoord * direction.zCoord);
|
||||
// Keep direction normalized
|
||||
double dirLen = Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);
|
||||
if (dirLen > 0.0001) {
|
||||
direction = Vec3.createVectorHelper(
|
||||
direction.xCoord / dirLen,
|
||||
direction.yCoord / dirLen,
|
||||
direction.zCoord / dirLen
|
||||
);
|
||||
dirX /= dirLen;
|
||||
dirY /= dirLen;
|
||||
dirZ /= dirLen;
|
||||
} else {
|
||||
direction = look != null ? look : Vec3.createVectorHelper(1.0, 0.0, 0.0);
|
||||
dirX = 1.0;
|
||||
dirY = 0.0;
|
||||
dirZ = 0.0;
|
||||
}
|
||||
|
||||
dirX = direction.xCoord;
|
||||
dirY = direction.yCoord;
|
||||
dirZ = direction.zCoord;
|
||||
|
||||
// Keep origin centered on look vector while active and clear owner bbox.
|
||||
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
|
||||
setLookVectorLaunchPosition(livingOwner, direction, false);
|
||||
// Lower beam origin slightly so it doesn't obscure the player's crosshair
|
||||
posY -= 0.15;
|
||||
syncStartPositionToCurrent();
|
||||
syncPositionStateToCurrent(false);
|
||||
}
|
||||
@@ -255,22 +236,24 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
public void startMoving(EntityLivingBase target) {
|
||||
beginLookVectorLaunch(false);
|
||||
|
||||
desiredLength = 0.0f;
|
||||
currentLength = 0.0f;
|
||||
fullyExtended = false;
|
||||
ticksSinceFullExtension = 0;
|
||||
lastBlockHitX = Integer.MIN_VALUE;
|
||||
lastBlockHitY = Integer.MIN_VALUE;
|
||||
lastBlockHitZ = Integer.MIN_VALUE;
|
||||
lastExplosionTick = -100;
|
||||
|
||||
// Set initial direction: target-based when available, otherwise owner look vector.
|
||||
// Set initial direction: owner look vector (sweeping beam always follows look)
|
||||
Vec3 look = getOwnerLookVector();
|
||||
if (!setDirectionTowardTarget(target, startX, startY, startZ)) {
|
||||
if (look != null) {
|
||||
dirX = look.xCoord;
|
||||
dirY = look.yCoord;
|
||||
dirZ = look.zCoord;
|
||||
} else {
|
||||
dirX = 1.0;
|
||||
dirY = 0.0;
|
||||
dirZ = 0.0;
|
||||
}
|
||||
if (look != null) {
|
||||
dirX = look.xCoord;
|
||||
dirY = look.yCoord;
|
||||
dirZ = look.zCoord;
|
||||
} else if (!setDirectionTowardTarget(target, startX, startY, startZ)) {
|
||||
dirX = 1.0;
|
||||
dirY = 0.0;
|
||||
dirZ = 0.0;
|
||||
}
|
||||
|
||||
// Initialize end point at start (will expand from here)
|
||||
@@ -294,40 +277,69 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
}
|
||||
|
||||
private void checkBlockCollision() {
|
||||
MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, endX, endY, endZ);
|
||||
if (currentLength <= 0) return;
|
||||
|
||||
double traceEndX = startX + dirX * currentLength;
|
||||
double traceEndY = startY + dirY * currentLength;
|
||||
double traceEndZ = startZ + dirZ * currentLength;
|
||||
|
||||
MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, traceEndX, traceEndY, traceEndZ);
|
||||
|
||||
if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
|
||||
if (!worldObj.isRemote) {
|
||||
EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ);
|
||||
}
|
||||
// Laser stops at block
|
||||
currentLength = (float) Math.sqrt(
|
||||
// Truncate beam at block hit
|
||||
float hitDist = (float) Math.sqrt(
|
||||
(blockHit.hitVec.xCoord - startX) * (blockHit.hitVec.xCoord - startX) +
|
||||
(blockHit.hitVec.yCoord - startY) * (blockHit.hitVec.yCoord - startY) +
|
||||
(blockHit.hitVec.zCoord - startZ) * (blockHit.hitVec.zCoord - startZ)
|
||||
);
|
||||
endX = blockHit.hitVec.xCoord;
|
||||
endY = blockHit.hitVec.yCoord;
|
||||
endZ = blockHit.hitVec.zCoord;
|
||||
fullyExtended = true;
|
||||
|
||||
if (isExplosive()) {
|
||||
// Explode at hit point
|
||||
double oldPosX = posX;
|
||||
double oldPosY = posY;
|
||||
double oldPosZ = posZ;
|
||||
posX = endX;
|
||||
posY = endY;
|
||||
posZ = endZ;
|
||||
doExplosion();
|
||||
posX = oldPosX;
|
||||
posY = oldPosY;
|
||||
posZ = oldPosZ;
|
||||
if (hitDist < currentLength) {
|
||||
currentLength = hitDist;
|
||||
endX = blockHit.hitVec.xCoord;
|
||||
endY = blockHit.hitVec.yCoord;
|
||||
endZ = blockHit.hitVec.zCoord;
|
||||
}
|
||||
|
||||
// Server-only: fire event and explosion only when block changes or cooldown expires
|
||||
if (!worldObj.isRemote) {
|
||||
int bx = blockHit.blockX;
|
||||
int by = blockHit.blockY;
|
||||
int bz = blockHit.blockZ;
|
||||
boolean newBlock = (bx != lastBlockHitX || by != lastBlockHitY || bz != lastBlockHitZ);
|
||||
|
||||
// Fire block impact event only when the beam hits a different block
|
||||
if (newBlock) {
|
||||
lastBlockHitX = bx;
|
||||
lastBlockHitY = by;
|
||||
lastBlockHitZ = bz;
|
||||
EventHooks.onEnergyProjectileBlockImpact(this, bx, by, bz);
|
||||
}
|
||||
|
||||
// Explosive: fire on cooldown so sweeping doesn't explode every tick
|
||||
if (isExplosive() && (ticksExisted - lastExplosionTick) >= EXPLOSION_COOLDOWN) {
|
||||
lastExplosionTick = ticksExisted;
|
||||
double oldPosX = posX;
|
||||
double oldPosY = posY;
|
||||
double oldPosZ = posZ;
|
||||
posX = endX;
|
||||
posY = endY;
|
||||
posZ = endZ;
|
||||
doExplosion();
|
||||
posX = oldPosX;
|
||||
posY = oldPosY;
|
||||
posZ = oldPosZ;
|
||||
}
|
||||
}
|
||||
} else if (!worldObj.isRemote) {
|
||||
// Beam is not hitting any block — reset tracking so next block contact triggers fresh
|
||||
lastBlockHitX = Integer.MIN_VALUE;
|
||||
lastBlockHitY = Integer.MIN_VALUE;
|
||||
lastBlockHitZ = Integer.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEntityCollisionAlongLine() {
|
||||
if (currentLength <= 0) return;
|
||||
|
||||
// Expand search AABB by entity sizes to ensure we find all potential targets
|
||||
double expand = Math.max(laserWidth, 1.0);
|
||||
double minX = Math.min(startX, endX) - expand;
|
||||
@@ -349,7 +361,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
// Check if entity's bounding box intersects with the laser line
|
||||
if (isEntityOnLine(entity)) {
|
||||
if (processEntityHit(entity, entity.posX, entity.posY + entity.height * 0.5, entity.posZ)) {
|
||||
// Stop at impact point and terminate
|
||||
// SINGLE hit or maxHits reached — truncate beam at impact and die
|
||||
double dx = entity.posX - startX;
|
||||
double dy = (entity.posY + entity.height * 0.5) - startY;
|
||||
double dz = entity.posZ - startZ;
|
||||
@@ -360,8 +372,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
endZ = startZ + dirZ * currentLength;
|
||||
return;
|
||||
}
|
||||
|
||||
// PIERCE/MULTI continue through remaining entities.
|
||||
// MULTI/PIERCE continue through remaining entities
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,24 +402,22 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
// ==================== BARRIER COLLISION ====================
|
||||
|
||||
/**
|
||||
* Override barrier collision for lasers. The base class uses projectile position/velocity
|
||||
* which doesn't work for lasers (posX/Y/Z stays at origin, motionX/Y/Z is zero).
|
||||
* Instead, performs line-sphere intersection against domes.
|
||||
* Disable the base class barrier check — it uses position/velocity which doesn't work
|
||||
* for lasers (posX/Y/Z stays at origin, motionX/Y/Z is zero). We handle barriers
|
||||
* exclusively in updateProjectile() via checkLaserBarrierCollision().
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected boolean checkBarrierCollision() {
|
||||
return checkBarrierCollisionInternal(false);
|
||||
}
|
||||
|
||||
private boolean checkBarrierCollisionForCurrentSegment() {
|
||||
return checkBarrierCollisionInternal(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laser-specific barrier collision using line-segment intersection.
|
||||
* Called from updateProjectile() every tick.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean checkBarrierCollisionInternal(boolean allowWhenFullyExtended) {
|
||||
private boolean checkLaserBarrierCollision() {
|
||||
if (currentLength <= 0) return false;
|
||||
if (fullyExtended && !allowWhenFullyExtended) return false;
|
||||
|
||||
List<EntityAbilityBarrier> barriers = EntityAbilityBarrier.getActiveBarriers(worldObj);
|
||||
for (EntityAbilityBarrier barrier : barriers) {
|
||||
@@ -441,15 +450,13 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
endZ = startZ + dirZ * currentLength;
|
||||
}
|
||||
|
||||
if (handleBarrierHitOutcome(dome, hitOutcome)) {
|
||||
if (handleBarrierHitOutcome(dome, hitOutcome, damage)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// For line-based lasers, use a swept segment against generic barrier checks.
|
||||
// Panel/Dome logic expects previous+current positions and motion; map those
|
||||
// to [start -> end] each tick.
|
||||
double segMotionX = endX - startX;
|
||||
double segMotionY = endY - startY;
|
||||
double segMotionZ = endZ - startZ;
|
||||
@@ -468,13 +475,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
}
|
||||
|
||||
if (hitOutcome.result != EntityAbilityBarrier.ProjectileHitResult.BROKEN) {
|
||||
// Keep current segment length at the exact collision tick position.
|
||||
endX = startX + dirX * currentLength;
|
||||
endY = startY + dirY * currentLength;
|
||||
endZ = startZ + dirZ * currentLength;
|
||||
}
|
||||
|
||||
if (handleBarrierHitOutcome(barrier, hitOutcome)) {
|
||||
if (handleBarrierHitOutcome(barrier, hitOutcome, damage)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -539,17 +545,29 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
setPosition(startX, startY, startZ);
|
||||
syncPositionStateToCurrent(true);
|
||||
|
||||
desiredLength = 0.0f;
|
||||
currentLength = 0.0f;
|
||||
fullyExtended = false;
|
||||
ticksSinceFullExtension = 0;
|
||||
endX = startX;
|
||||
endY = startY;
|
||||
endZ = startZ;
|
||||
lastBlockHitX = Integer.MIN_VALUE;
|
||||
lastBlockHitY = Integer.MIN_VALUE;
|
||||
lastBlockHitZ = Integer.MIN_VALUE;
|
||||
lastExplosionTick = -100;
|
||||
|
||||
// Reset lifetime so reflected laser gets a fresh maxLifetime from the base class
|
||||
deathWorldTime = -1;
|
||||
|
||||
reflectedLockTicks = Math.max(reflectedLockTicks, 10);
|
||||
barrierImpactPauseTicks = 0;
|
||||
barrierImpactDestroyOnResume = false;
|
||||
|
||||
// Detach from source ability so the caster is freed from active-phase locks.
|
||||
if (sourceAbility instanceof AbilityEnergyProjectile) {
|
||||
((AbilityEnergyProjectile<?>) sourceAbility).detachEntity(this);
|
||||
}
|
||||
sourceAbility = null;
|
||||
|
||||
Entity barrierOwner = barrier.getOwnerEntity();
|
||||
if (barrierOwner != null) {
|
||||
setOwnerEntityId(barrierOwner.getEntityId());
|
||||
@@ -566,6 +584,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
|
||||
hitOnceEntities.clear();
|
||||
lastHitTickByEntity.clear();
|
||||
reflected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -588,7 +607,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
if (originDistSq < (double) radius * radius) return -1;
|
||||
|
||||
// Solve: |start + t*dir - C|^2 = R^2
|
||||
// a*t^2 + b*t + c = 0
|
||||
double a = dirX * dirX + dirY * dirY + dirZ * dirZ;
|
||||
double b = 2.0 * (dirX * ocX + dirY * ocY + dirZ * ocZ);
|
||||
double c = originDistSq - (double) radius * radius;
|
||||
@@ -606,15 +624,18 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ==================== GETTERS FOR RENDERER ====================
|
||||
// ==================== DEBUG ====================
|
||||
|
||||
public void setLockVerticalDirection(boolean lock) {
|
||||
this.lockVerticalDirection = lock;
|
||||
@Override
|
||||
protected String debugLogExtra() {
|
||||
return String.format("dir=(%.3f,%.3f,%.3f) desired=%.2f current=%.2f max=%.2f " +
|
||||
"fullyExtended=%b end=(%.2f,%.2f,%.2f) width=%.2f expSpd=%.2f origin=(%.2f,%.2f,%.2f)",
|
||||
dirX, dirY, dirZ, desiredLength, currentLength, maxLength,
|
||||
fullyExtended, endX, endY, endZ, laserWidth, expansionSpeed,
|
||||
startX, startY, startZ);
|
||||
}
|
||||
|
||||
public boolean isLockVerticalDirection() {
|
||||
return lockVerticalDirection;
|
||||
}
|
||||
// ==================== GETTERS / SETTERS ====================
|
||||
|
||||
public float getLaserWidth() {
|
||||
return laserWidth;
|
||||
@@ -632,12 +653,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
this.expansionSpeed = speed;
|
||||
}
|
||||
|
||||
public int getLingerTicks() {
|
||||
return lingerTicks;
|
||||
public float getMaxLength() {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
public void setLingerTicks(int ticks) {
|
||||
this.lingerTicks = ticks;
|
||||
public void setMaxLength(float maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
public void setDirection(double x, double y, double z) {
|
||||
@@ -690,15 +711,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
return dirZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha for fade-out effect during linger.
|
||||
*/
|
||||
public float getLingerAlpha() {
|
||||
if (!fullyExtended) return 1.0f;
|
||||
if (lingerTicks <= 0) return 0.0f;
|
||||
return Math.max(0.0f, 1.0f - ((float) ticksSinceFullExtension / lingerTicks));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getLaunchClearanceRadius() {
|
||||
return Math.max(0.1f, laserWidth * 0.5f);
|
||||
@@ -706,17 +718,53 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
|
||||
@Override
|
||||
public void setupCharging(EnergyAnchorData anchor, int chargeDuration) {
|
||||
// Ensure charging visuals grow from a small orb based on laser width.
|
||||
this.size = laserWidth;
|
||||
// Charge orb grows to half the beam width
|
||||
this.size = laserWidth * 0.5f;
|
||||
this.desiredLength = 0.0f;
|
||||
this.currentLength = 0.0f;
|
||||
this.fullyExtended = false;
|
||||
this.ticksSinceFullExtension = 0;
|
||||
super.setupCharging(anchor, chargeDuration);
|
||||
this.endX = posX;
|
||||
this.endY = posY;
|
||||
this.endZ = posZ;
|
||||
}
|
||||
|
||||
// ==================== REFLECTION SYNC ====================
|
||||
|
||||
@Override
|
||||
protected void writeProjectileReflectionData(NBTTagCompound nbt) {
|
||||
nbt.setDouble("DirX", dirX);
|
||||
nbt.setDouble("DirY", dirY);
|
||||
nbt.setDouble("DirZ", dirZ);
|
||||
nbt.setDouble("StartX", startX);
|
||||
nbt.setDouble("StartY", startY);
|
||||
nbt.setDouble("StartZ", startZ);
|
||||
nbt.setFloat("DesiredLength", desiredLength);
|
||||
nbt.setFloat("CurrentLength", currentLength);
|
||||
nbt.setDouble("EndX", endX);
|
||||
nbt.setDouble("EndY", endY);
|
||||
nbt.setDouble("EndZ", endZ);
|
||||
nbt.setBoolean("FullyExtended", fullyExtended);
|
||||
nbt.setFloat("MaxLength", maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyProjectileReflectionData(NBTTagCompound nbt) {
|
||||
dirX = nbt.getDouble("DirX");
|
||||
dirY = nbt.getDouble("DirY");
|
||||
dirZ = nbt.getDouble("DirZ");
|
||||
startX = nbt.getDouble("StartX");
|
||||
startY = nbt.getDouble("StartY");
|
||||
startZ = nbt.getDouble("StartZ");
|
||||
desiredLength = nbt.getFloat("DesiredLength");
|
||||
currentLength = nbt.getFloat("CurrentLength");
|
||||
endX = nbt.getDouble("EndX");
|
||||
endY = nbt.getDouble("EndY");
|
||||
endZ = nbt.getDouble("EndZ");
|
||||
fullyExtended = nbt.getBoolean("FullyExtended");
|
||||
if (nbt.hasKey("MaxLength")) maxLength = nbt.getFloat("MaxLength");
|
||||
}
|
||||
|
||||
// ==================== NBT ====================
|
||||
|
||||
@Override
|
||||
@@ -724,13 +772,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
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.lingerTicks = nbt.hasKey("LingerTicks") ? nbt.getInteger("LingerTicks") : 10;
|
||||
if (lingerTicks <= 0) lingerTicks = 1;
|
||||
this.lockVerticalDirection = nbt.getBoolean("LockVerticalDir");
|
||||
this.ticksSinceFullExtension = nbt.getInteger("TicksSinceExtended");
|
||||
this.maxLength = nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f;
|
||||
if (Float.isNaN(maxLength) || Float.isInfinite(maxLength) || maxLength <= 0) maxLength = 32.0f;
|
||||
this.dirX = nbt.getDouble("DirX");
|
||||
this.dirY = nbt.getDouble("DirY");
|
||||
this.dirZ = nbt.getDouble("DirZ");
|
||||
this.desiredLength = nbt.getFloat("DesiredLength");
|
||||
this.currentLength = nbt.getFloat("CurrentLength");
|
||||
this.fullyExtended = nbt.getBoolean("FullyExtended");
|
||||
this.endX = nbt.getDouble("EndX");
|
||||
@@ -744,12 +791,11 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
protected void writeProjectileNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("LaserWidth", laserWidth);
|
||||
nbt.setFloat("ExpansionSpeed", expansionSpeed);
|
||||
nbt.setInteger("LingerTicks", lingerTicks);
|
||||
nbt.setBoolean("LockVerticalDir", lockVerticalDirection);
|
||||
nbt.setInteger("TicksSinceExtended", ticksSinceFullExtension);
|
||||
nbt.setFloat("MaxLength", maxLength);
|
||||
nbt.setDouble("DirX", dirX);
|
||||
nbt.setDouble("DirY", dirY);
|
||||
nbt.setDouble("DirZ", dirZ);
|
||||
nbt.setFloat("DesiredLength", desiredLength);
|
||||
nbt.setFloat("CurrentLength", currentLength);
|
||||
nbt.setBoolean("FullyExtended", fullyExtended);
|
||||
nbt.setDouble("EndX", endX);
|
||||
@@ -765,19 +811,19 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
*/
|
||||
public void setupPreview(EntityLivingBase owner, float laserWidth, EnergyDisplayData display,
|
||||
EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration,
|
||||
float expansionSpeed, float maxDistance) {
|
||||
float expansionSpeed, float maxLength) {
|
||||
setupPreviewState(owner, display, lightning, anchor, chargeDuration);
|
||||
|
||||
// Laser-specific visual/behavior state.
|
||||
this.laserWidth = laserWidth;
|
||||
this.expansionSpeed = expansionSpeed;
|
||||
this.lifespanData.maxDistance = Math.min(maxDistance, 5.0f); // Limit for GUI preview
|
||||
this.maxLength = Math.min(maxLength, 5.0f); // Limit for GUI preview
|
||||
this.desiredLength = 0.0f;
|
||||
this.currentLength = 0.0f;
|
||||
this.fullyExtended = false;
|
||||
this.ticksSinceFullExtension = 0;
|
||||
|
||||
// Charge as an orb from tiny size to laser width while anchored.
|
||||
this.targetSize = laserWidth;
|
||||
// Charge as an orb from tiny size to half laser width while anchored.
|
||||
this.targetSize = laserWidth * 0.5f;
|
||||
setVisualSize(0.01f);
|
||||
setChargeOriginFromAnchor(owner, anchorData);
|
||||
clearMotion();
|
||||
|
||||
@@ -38,7 +38,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
|
||||
// Initialize base properties
|
||||
initProjectile(owner, target, x, y, z, orbSize, display, combat, lightning, lifespan);
|
||||
|
||||
this.homingData = homing;
|
||||
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
|
||||
|
||||
// Calculate initial velocity toward target
|
||||
calculateInitialVelocity(owner, target, x, y, z);
|
||||
@@ -150,6 +150,16 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
|
||||
processEntitiesInHitBox(hitBox, nextX, nextY, nextZ);
|
||||
}
|
||||
|
||||
// ==================== DEBUG ====================
|
||||
|
||||
@Override
|
||||
protected String debugLogExtra() {
|
||||
return String.format("motion=(%.3f,%.3f,%.3f) homing=%b start=(%.2f,%.2f,%.2f)",
|
||||
motionX, motionY, motionZ,
|
||||
homingData != null && homingData.isHoming(),
|
||||
startX, startY, startZ);
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
// Legacy getter for renderer compatibility
|
||||
|
||||
@@ -56,11 +56,12 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
|
||||
this.panelYaw = yaw;
|
||||
this.prevPanelYaw = yaw;
|
||||
this.mode = mode;
|
||||
this.displayData = display;
|
||||
this.lightningData = lightning;
|
||||
this.barrierData = barrier;
|
||||
this.panelData = panel;
|
||||
this.currentHealth = barrier.maxHealth;
|
||||
// Defensive copy: entities must never share data objects with the source ability.
|
||||
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
|
||||
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
|
||||
this.barrierData = barrier != null ? barrier.copy() : new EnergyBarrierData();
|
||||
this.panelData = panel != null ? panel.copy() : new EnergyPanelData();
|
||||
this.currentHealth = this.barrierData.maxHealth;
|
||||
|
||||
// For launched panels, set initial velocity
|
||||
if (mode == PanelMode.LAUNCHED) {
|
||||
@@ -142,6 +143,8 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
|
||||
// Stationary, no update needed
|
||||
break;
|
||||
}
|
||||
|
||||
debugLogBarrierTick();
|
||||
}
|
||||
|
||||
private void updateHeld() {
|
||||
@@ -175,7 +178,10 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
|
||||
if (worldObj != null && worldObj.isRemote && mode == PanelMode.HELD && getOwnerEntity() != null) {
|
||||
return;
|
||||
}
|
||||
super.setPositionAndRotation2(x, y, z, yaw, pitch, posRotationIncrements);
|
||||
// Bypass vanilla's lerp + push-out-of-blocks logic which causes
|
||||
// large-BB entities (PLACED/LAUNCHED panels) to shift into the sky.
|
||||
this.setPosition(x, y, z);
|
||||
this.setRotation(yaw, pitch);
|
||||
}
|
||||
|
||||
private void updateLaunched() {
|
||||
@@ -544,6 +550,14 @@ public class EntityAbilityPanel extends EntityAbilityBarrier {
|
||||
return df * df + dr * dr + du * du;
|
||||
}
|
||||
|
||||
// ==================== DEBUG ====================
|
||||
|
||||
@Override
|
||||
protected String debugLogBarrierExtra() {
|
||||
return String.format("mode=%s yaw=%.1f w=%.2f h=%.2f",
|
||||
mode.name(), panelYaw, panelData.panelWidth, panelData.panelHeight);
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public PanelMode getMode() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package kamkeel.npcs.entity;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -79,7 +80,7 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
|
||||
this.beamLength = beamLength;
|
||||
this.beamWidth = beamWidth;
|
||||
this.beamHeight = beamHeight;
|
||||
this.displayData = displayData;
|
||||
this.displayData = displayData != null ? displayData.copy() : new EnergyDisplayData();
|
||||
this.sweepSpeed = sweepSpeed;
|
||||
this.numberOfRotations = numberOfRotations;
|
||||
this.damage = damage;
|
||||
@@ -168,6 +169,19 @@ public class EntityAbilitySweeper extends EntityEnergyAbility {
|
||||
if (!worldObj.isRemote) {
|
||||
handleDamage(owner);
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
{
|
||||
boolean isClient = worldObj.isRemote;
|
||||
if (isClient ? CNPCDebug.isClientEnabled("energy") : CNPCDebug.isServerEnabled("energy")) {
|
||||
CNPCDebug.log("energy", isClient, String.format(
|
||||
"[Sweeper id=%d tick=%d] pos=(%.2f,%.2f,%.2f) angle=%.1f prevAngle=%.1f " +
|
||||
"speed=%.1f rotations=%d/%d length=%.2f width=%.2f",
|
||||
getEntityId(), ticksExisted, posX, posY, posZ,
|
||||
currentAngle, prevAngle, sweepSpeed,
|
||||
completedRotations, numberOfRotations, beamLength, beamWidth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
@@ -57,6 +58,17 @@ public class EntityEnergyExplosion extends EntityEnergyAbility {
|
||||
float eased = 1.0f - (1.0f - progress) * (1.0f - progress);
|
||||
this.renderRadius = maxRadius * eased;
|
||||
|
||||
// Debug logging
|
||||
{
|
||||
boolean isClient = worldObj.isRemote;
|
||||
if (isClient ? CNPCDebug.isClientEnabled("energy") : CNPCDebug.isServerEnabled("energy")) {
|
||||
CNPCDebug.log("energy", isClient, String.format(
|
||||
"[Explosion id=%d tick=%d] pos=(%.2f,%.2f,%.2f) renderRadius=%.2f maxRadius=%.2f progress=%.2f",
|
||||
getEntityId(), ticksExisted, posX, posY, posZ,
|
||||
renderRadius, maxRadius, getLifeProgress(0f)));
|
||||
}
|
||||
}
|
||||
|
||||
if (ticksExisted >= durationTicks) {
|
||||
setDead();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package kamkeel.npcs.entity;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityEnergyProjectile;
|
||||
import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.HitType;
|
||||
@@ -12,8 +13,10 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
||||
import kamkeel.npcs.network.packets.data.energy.ProjectileReflectPacket;
|
||||
import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -120,6 +123,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
protected int targetEntityId = -1;
|
||||
|
||||
// ==================== STATE ====================
|
||||
protected boolean reflected = false; // Set after barrier reflection; prevents owner-tracking on reflected projectiles
|
||||
protected boolean hasHit = false;
|
||||
protected int hitCount = 0;
|
||||
protected final Set<Integer> hitOnceEntities = new HashSet<Integer>();
|
||||
@@ -183,10 +187,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
// Visual
|
||||
this.size = size;
|
||||
|
||||
this.displayData = display;
|
||||
this.combatData = combat;
|
||||
this.lifespanData = lifespan; // deathWorldTime will be set on first tick when world is available
|
||||
this.lightningData = lightning;
|
||||
// Defensive copy: entities must never share data objects with the source ability.
|
||||
// Without copies, runtime mutations (e.g. barrier reflection changing colors/damage)
|
||||
// bleed back into the ability template and corrupt future projectiles.
|
||||
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
|
||||
this.combatData = combat != null ? combat.copy() : new EnergyCombatData();
|
||||
this.lifespanData = lifespan != null ? lifespan.copy() : new EnergyLifespanData();
|
||||
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
|
||||
syncProjectileColorWatchers();
|
||||
|
||||
// Initialize render size
|
||||
@@ -221,6 +228,10 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
if (ticksExisted == 1 && !worldObj.isRemote) {
|
||||
trackProjectile(this);
|
||||
}
|
||||
} else {
|
||||
// Preview entities skip super.onUpdate() but still need ticksExisted
|
||||
// for renderer pulsing effects (e.g., sin(ticksExisted + partialTicks)).
|
||||
this.ticksExisted++;
|
||||
}
|
||||
|
||||
if (!previewMode) {
|
||||
@@ -230,8 +241,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
// Update rotation
|
||||
updateRotation();
|
||||
|
||||
// Lerp render size toward actual size
|
||||
this.renderCurrentSize = this.renderCurrentSize + (this.size - this.renderCurrentSize) * 0.15f;
|
||||
// Update render size: during charging, track directly (size already grows smoothly).
|
||||
// Post-charging, lerp for smooth visual transitions.
|
||||
if (isCharging()) {
|
||||
this.renderCurrentSize = this.size;
|
||||
} else {
|
||||
this.renderCurrentSize = this.renderCurrentSize + (this.size - this.renderCurrentSize) * 0.15f;
|
||||
}
|
||||
|
||||
// Server-synced visual timer used for brief barrier impact lightning.
|
||||
if (!previewMode && !worldObj.isRemote) {
|
||||
@@ -319,6 +335,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
if (!previewMode && !worldObj.isRemote) {
|
||||
EventHooks.onEnergyProjectileTick(this);
|
||||
}
|
||||
|
||||
debugLogTick();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,6 +352,53 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
if (this.rotationValZ > 360.0f) this.rotationValZ -= 360.0f;
|
||||
}
|
||||
|
||||
// ==================== DEBUG LOGGING ====================
|
||||
|
||||
/**
|
||||
* Debug log called every tick. Logs position/size/charging state.
|
||||
* Subclasses override debugLogExtra() for type-specific data.
|
||||
*/
|
||||
protected void debugLogTick() {
|
||||
boolean isClient = worldObj.isRemote;
|
||||
if (isClient ? !CNPCDebug.isClientEnabled("energy") : !CNPCDebug.isServerEnabled("energy"))
|
||||
return;
|
||||
|
||||
String className = getClass().getSimpleName();
|
||||
boolean dwCharging = isCharging(); // DataWatcher value (what client sees)
|
||||
boolean localCharging = this.charging; // Local field (server truth)
|
||||
|
||||
String base = String.format("[%s id=%d tick=%d] pos=(%.2f,%.2f,%.2f) prev=(%.2f,%.2f,%.2f) " +
|
||||
"size=%.3f renderSize=%.3f prevRenderSize=%.3f " +
|
||||
"charging(dw)=%b charging(local)=%b chargeTick=%d/%d chargeProgress=%.3f",
|
||||
className, getEntityId(), ticksExisted,
|
||||
posX, posY, posZ, prevPosX, prevPosY, prevPosZ,
|
||||
size, renderCurrentSize, prevRenderSize,
|
||||
dwCharging, localCharging, chargeTick, chargeDuration, getChargeProgress());
|
||||
|
||||
// Flag mismatch between DataWatcher and local field
|
||||
if (dwCharging != localCharging) {
|
||||
base += " !!CHARGE_MISMATCH!!";
|
||||
}
|
||||
|
||||
// Client interpolation state (only meaningful on client)
|
||||
if (isClient) {
|
||||
base += String.format(" interp(steps=%d target=(%.2f,%.2f,%.2f))",
|
||||
interpSteps, interpTargetX, interpTargetY, interpTargetZ);
|
||||
}
|
||||
|
||||
String extra = debugLogExtra();
|
||||
String full = extra.isEmpty() ? base : base + " " + extra;
|
||||
CNPCDebug.log("energy", isClient, full);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook for type-specific debug data.
|
||||
* Return empty string if nothing extra to log.
|
||||
*/
|
||||
protected String debugLogExtra() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if projectile has exceeded max distance.
|
||||
*
|
||||
@@ -380,7 +445,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) {
|
||||
continue;
|
||||
}
|
||||
if (handleBarrierHitOutcome(barrier, hitOutcome)) {
|
||||
if (handleBarrierHitOutcome(barrier, hitOutcome, damage)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -393,20 +458,29 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
/**
|
||||
* Handle the result of a projectile/barrier interaction.
|
||||
*
|
||||
* @param fullDamage The DBC-scaled damage that was passed to the barrier (from getModifiedDamage).
|
||||
* @return true if caller should stop processing this tick.
|
||||
*/
|
||||
protected boolean handleBarrierHitOutcome(EntityAbilityBarrier barrier, EntityAbilityBarrier.ProjectileHitOutcome hitOutcome) {
|
||||
protected boolean handleBarrierHitOutcome(EntityAbilityBarrier barrier, EntityAbilityBarrier.ProjectileHitOutcome hitOutcome, float fullDamage) {
|
||||
if (hitOutcome == null || hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.PASS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hitOutcome.result == EntityAbilityBarrier.ProjectileHitResult.BROKEN) {
|
||||
if (hitOutcome.remainingProjectileDamage <= 0.0f) {
|
||||
damageMultiplier = 0.0f;
|
||||
setBarrierSparkTicks(Math.max(getBarrierSparkTicks(), BARRIER_BREAK_SPARK_TICKS));
|
||||
hasHit = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Accumulate barrier pass-through ratio for extender damage scaling.
|
||||
// fullDamage is the DBC-scaled damage the barrier saw; remainingProjectileDamage
|
||||
// is what survived. Multiply into the running ratio so multiple barriers stack.
|
||||
if (fullDamage > 0.0f) {
|
||||
damageMultiplier *= (hitOutcome.remainingProjectileDamage / fullDamage);
|
||||
}
|
||||
|
||||
setCombatDamage(hitOutcome.remainingProjectileDamage);
|
||||
setBarrierSparkTicks(Math.max(getBarrierSparkTicks(), BARRIER_BREAK_SPARK_TICKS));
|
||||
return false;
|
||||
@@ -415,6 +489,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
if (hitOutcome.shouldReflect()) {
|
||||
if (!reflectFromBarrier(barrier, hitOutcome.reflectStrengthPct)) {
|
||||
beginBarrierImpactPause(true, barrier);
|
||||
} else {
|
||||
sendReflectionSync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -486,6 +562,14 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
barrierImpactPauseTicks = 0;
|
||||
barrierImpactDestroyOnResume = false;
|
||||
|
||||
// Detach from source ability BEFORE changing ownership.
|
||||
// This lets the ability see the projectile as gone and free the caster
|
||||
// (e.g., release movement/rotation locks on the player).
|
||||
if (sourceAbility instanceof AbilityEnergyProjectile) {
|
||||
((AbilityEnergyProjectile<?>) sourceAbility).detachEntity(this);
|
||||
}
|
||||
sourceAbility = null;
|
||||
|
||||
Entity barrierOwner = barrier.getOwnerEntity();
|
||||
if (barrierOwner != null) {
|
||||
setOwnerEntityId(barrierOwner.getEntityId());
|
||||
@@ -499,15 +583,83 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
setOuterColor(barrier.getOuterColor());
|
||||
|
||||
float clampedStrength = Math.max(0.0f, Math.min(100.0f, reflectStrengthPct));
|
||||
float reducedDamage = getDamage() * (1.0f - clampedStrength / 100.0f);
|
||||
float reductionFactor = 1.0f - clampedStrength / 100.0f;
|
||||
float reducedDamage = getDamage() * reductionFactor;
|
||||
setCombatDamage(Math.max(0.0f, reducedDamage));
|
||||
damageMultiplier *= reductionFactor;
|
||||
|
||||
hitOnceEntities.clear();
|
||||
lastHitTickByEntity.clear();
|
||||
hitCount = 0;
|
||||
reflected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== REFLECTION SYNC ====================
|
||||
|
||||
/**
|
||||
* Write reflection state for client sync.
|
||||
* Subclasses should override writeProjectileReflectionData for type-specific fields.
|
||||
*/
|
||||
public NBTTagCompound writeReflectionData() {
|
||||
NBTTagCompound nbt = new NBTTagCompound();
|
||||
nbt.setInteger("OwnerId", ownerEntityId);
|
||||
nbt.setDouble("PosX", posX);
|
||||
nbt.setDouble("PosY", posY);
|
||||
nbt.setDouble("PosZ", posZ);
|
||||
nbt.setDouble("MotionX", motionX);
|
||||
nbt.setDouble("MotionY", motionY);
|
||||
nbt.setDouble("MotionZ", motionZ);
|
||||
nbt.setInteger("InnerColor", getInnerColor());
|
||||
nbt.setInteger("OuterColor", getOuterColor());
|
||||
nbt.setBoolean("Reflected", reflected);
|
||||
writeProjectileReflectionData(nbt);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply reflection state from server sync.
|
||||
* Subclasses should override applyProjectileReflectionData for type-specific fields.
|
||||
*/
|
||||
public void applyReflectionData(NBTTagCompound nbt) {
|
||||
ownerEntityId = nbt.getInteger("OwnerId");
|
||||
double px = nbt.getDouble("PosX");
|
||||
double py = nbt.getDouble("PosY");
|
||||
double pz = nbt.getDouble("PosZ");
|
||||
setPosition(px, py, pz);
|
||||
motionX = nbt.getDouble("MotionX");
|
||||
motionY = nbt.getDouble("MotionY");
|
||||
motionZ = nbt.getDouble("MotionZ");
|
||||
setInnerColor(nbt.getInteger("InnerColor"));
|
||||
setOuterColor(nbt.getInteger("OuterColor"));
|
||||
reflected = nbt.getBoolean("Reflected");
|
||||
syncPositionStateToCurrent(true);
|
||||
hitOnceEntities.clear();
|
||||
lastHitTickByEntity.clear();
|
||||
hitCount = 0;
|
||||
applyProjectileReflectionData(nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook: write type-specific reflection data.
|
||||
*/
|
||||
protected void writeProjectileReflectionData(NBTTagCompound nbt) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook: apply type-specific reflection data on client.
|
||||
*/
|
||||
protected void applyProjectileReflectionData(NBTTagCompound nbt) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send reflection state to all tracking clients.
|
||||
*/
|
||||
protected void sendReflectionSync() {
|
||||
if (worldObj == null || worldObj.isRemote) return;
|
||||
ProjectileReflectPacket.sendToTracking(this, writeReflectionData());
|
||||
}
|
||||
|
||||
protected double[] getBarrierImpactNormal(EntityAbilityBarrier barrier, double velocityX, double velocityY, double velocityZ) {
|
||||
if (barrier instanceof EntityAbilityDome) {
|
||||
EntityAbilityDome dome = (EntityAbilityDome) barrier;
|
||||
@@ -857,7 +1009,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
float kbUp = getKnockbackUp() > 0 ? getKnockbackUp() : 0.1f;
|
||||
handled = AbilityController.Instance.fireOnAbilityDamage(
|
||||
sourceAbility, (EntityLivingBase) owner, target,
|
||||
dmg, kb, kbUp, dx, dz);
|
||||
dmg, kb, kbUp, dx, dz, damageMultiplier);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
@@ -902,7 +1054,15 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
* Set the effects list from the ability's configured effects.
|
||||
*/
|
||||
public void setEffects(List<AbilityPotionEffect> effects) {
|
||||
this.effects = effects != null ? effects : new ArrayList<>();
|
||||
if (effects == null || effects.isEmpty()) {
|
||||
this.effects = new ArrayList<>();
|
||||
} else {
|
||||
// Deep copy: entity must not share the ability's effects list or its entries.
|
||||
this.effects = new ArrayList<>(effects.size());
|
||||
for (AbilityPotionEffect effect : effects) {
|
||||
this.effects.add(effect.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void doExplosion() {
|
||||
@@ -1509,8 +1669,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
int chargeDuration) {
|
||||
this.setPreviewMode(true);
|
||||
this.setPreviewOwner(owner);
|
||||
this.displayData = display;
|
||||
this.lightningData = lightning;
|
||||
this.displayData = display != null ? display.copy() : new EnergyDisplayData();
|
||||
this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData();
|
||||
setupChargingState(anchor, chargeDuration);
|
||||
}
|
||||
|
||||
@@ -1817,6 +1977,18 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
|
||||
// ==================== COMBAT GETTERS & SETTERS ====================
|
||||
|
||||
/**
|
||||
* Accumulated proportional damage multiplier (0.0-1.0).
|
||||
* Accumulates multiplicatively when the projectile breaks through barriers, is reflected,
|
||||
* or any other system reduces its effective damage. Used by ability extenders to scale
|
||||
* recalculated damage proportionally.
|
||||
*/
|
||||
private float damageMultiplier = 1.0f;
|
||||
|
||||
public float getDamageMultiplier() {
|
||||
return damageMultiplier;
|
||||
}
|
||||
|
||||
public float getDamage() {
|
||||
return combatData.getDamage();
|
||||
}
|
||||
@@ -2167,6 +2339,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
this.startZ = nbt.getDouble("StartZ");
|
||||
|
||||
this.targetEntityId = nbt.getInteger("TargetId");
|
||||
this.reflected = nbt.getBoolean("Reflected");
|
||||
|
||||
this.motionX = nbt.getDouble("MotionX");
|
||||
this.motionY = nbt.getDouble("MotionY");
|
||||
@@ -2223,6 +2396,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
nbt.setDouble("StartZ", startZ);
|
||||
|
||||
nbt.setInteger("TargetId", targetEntityId);
|
||||
nbt.setBoolean("Reflected", reflected);
|
||||
|
||||
nbt.setDouble("MotionX", motionX);
|
||||
nbt.setDouble("MotionY", motionY);
|
||||
|
||||
@@ -43,7 +43,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile {
|
||||
super(world);
|
||||
|
||||
initProjectile(owner, target, x, y, z, sliceWidth, display, combat, lightning, lifespan);
|
||||
this.homingData = homing;
|
||||
this.homingData = homing != null ? homing.copy() : new EnergyHomingData();
|
||||
this.sliceWidth = sliceWidth;
|
||||
this.sliceThickness = sliceThickness;
|
||||
|
||||
@@ -192,6 +192,14 @@ public class EntityEnergySlicer extends EntityEnergyProjectile {
|
||||
return lastTravelPitch;
|
||||
}
|
||||
|
||||
// ==================== DEBUG ====================
|
||||
|
||||
@Override
|
||||
protected String debugLogExtra() {
|
||||
return String.format("motion=(%.3f,%.3f,%.3f) sliceW=%.2f sliceT=%.2f",
|
||||
motionX, motionY, motionZ, sliceWidth, sliceThickness);
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public float getSliceWidth() {
|
||||
|
||||
@@ -39,6 +39,7 @@ import kamkeel.npcs.network.packets.data.script.ScriptedParticlePacket;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket;
|
||||
import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket;
|
||||
import kamkeel.npcs.network.packets.data.energy.ProjectileReflectPacket;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
|
||||
import kamkeel.npcs.network.packets.player.AuctionActionPacket;
|
||||
@@ -592,6 +593,7 @@ public class PacketHandler {
|
||||
DATA_PACKET.registerPacket(new EnergyChargeSpawnPacket());
|
||||
DATA_PACKET.registerPacket(new EnergyChargeRemovePacket());
|
||||
DATA_PACKET.registerPacket(new EnergyExplosionSpawnPacket());
|
||||
DATA_PACKET.registerPacket(new ProjectileReflectPacket());
|
||||
DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket());
|
||||
DATA_PACKET.registerPacket(new PlayerAbilityStatePacket());
|
||||
DATA_PACKET.registerPacket(new AbilityHotbarSyncPacket());
|
||||
|
||||
@@ -76,4 +76,7 @@ public enum EnumDataPacket {
|
||||
|
||||
// Energy Explosion Preview
|
||||
ENERGY_EXPLOSION_SPAWN,
|
||||
|
||||
// Energy Projectile Reflection Sync
|
||||
PROJECTILE_REFLECT,
|
||||
}
|
||||
+2
-1
@@ -10,6 +10,7 @@ import kamkeel.npcs.network.enums.EnumDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.controllers.PlayerDataController;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
|
||||
@@ -86,7 +87,7 @@ public final class AbilityCooldownSyncPacket extends AbstractPacket {
|
||||
perDurations.put(key, duration);
|
||||
}
|
||||
|
||||
PlayerData data = PlayerData.get(player);
|
||||
PlayerData data = ClientCacheHandler.playerData;
|
||||
if (data != null && data.abilityData != null) {
|
||||
data.abilityData.applyCooldownSync(globalEndTime, globalDuration, perEndTimes, perDurations);
|
||||
}
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.controllers.PlayerDataController;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
|
||||
@@ -53,7 +54,7 @@ public final class PlayerAbilitySyncPacket extends AbstractPacket {
|
||||
NBTTagCompound nbt = ByteBufUtils.readNBT(in);
|
||||
if (nbt == null) return;
|
||||
|
||||
PlayerData data = PlayerData.get(player);
|
||||
PlayerData data = ClientCacheHandler.playerData;
|
||||
if (data != null) {
|
||||
data.abilityData.readFromNBT(nbt);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package kamkeel.npcs.network.packets.data.energy;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.enums.EnumDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Syncs projectile reflection state to all tracking clients.
|
||||
* Sent server→client after a successful barrier reflection so the client
|
||||
* can update ownerEntityId, position, motion, colors, and type-specific
|
||||
* reflection data (beam mode, laser direction, disc boomerang, etc.).
|
||||
*/
|
||||
public final class ProjectileReflectPacket extends AbstractPacket {
|
||||
public static final String packetName = "Data|ProjectileReflect";
|
||||
|
||||
private int entityId;
|
||||
private NBTTagCompound reflectData;
|
||||
|
||||
public ProjectileReflectPacket() {
|
||||
}
|
||||
|
||||
public ProjectileReflectPacket(int entityId, NBTTagCompound reflectData) {
|
||||
this.entityId = entityId;
|
||||
this.reflectData = reflectData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumDataPacket.PROJECTILE_REFLECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.DATA_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(entityId);
|
||||
ByteBufUtils.writeNBT(out, reflectData);
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
int id = in.readInt();
|
||||
NBTTagCompound nbt = ByteBufUtils.readNBT(in);
|
||||
if (nbt == null) return;
|
||||
|
||||
World world = Minecraft.getMinecraft().theWorld;
|
||||
if (world == null) return;
|
||||
|
||||
Entity entity = world.getEntityByID(id);
|
||||
if (entity instanceof EntityEnergyProjectile) {
|
||||
((EntityEnergyProjectile) entity).applyReflectionData(nbt);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendToTracking(EntityEnergyProjectile projectile, NBTTagCompound reflectData) {
|
||||
if (projectile == null || reflectData == null) return;
|
||||
PacketHandler.Instance.sendTracking(
|
||||
new ProjectileReflectPacket(projectile.getEntityId(), reflectData),
|
||||
projectile
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -311,6 +311,10 @@ public class AuctionActionPacket extends AbstractPacket {
|
||||
ItemStack item = ItemStack.loadItemStackFromNBT(itemNBT);
|
||||
if (item == null) return "Invalid item data.";
|
||||
|
||||
// Validate stack size
|
||||
if (item.stackSize <= 0) return "Invalid item amount.";
|
||||
if (item.stackSize > item.getMaxStackSize()) return "Stack size exceeds item maximum.";
|
||||
|
||||
// Remove item from inventory first
|
||||
if (!removeItemFromInventory(player, item)) {
|
||||
return "Item not found in inventory.";
|
||||
|
||||
@@ -366,8 +366,9 @@ public class ManageAuctionPacket extends AbstractPacket {
|
||||
String result = controller.createGlobalListing(player, sellerName, item, startingPrice, buyoutPrice, durationHours);
|
||||
if (result != null) {
|
||||
// Failed create, return item (or drop to avoid loss).
|
||||
if (!player.inventory.addItemStackToInventory(item.copy())) {
|
||||
player.entityDropItem(item.copy(), 0.5f);
|
||||
// Use same reference so partial add mutates stackSize, then drop only the remainder.
|
||||
if (!player.inventory.addItemStackToInventory(item)) {
|
||||
player.entityDropItem(item, 0.5f);
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package kamkeel.npcs.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.StreamHandler;
|
||||
|
||||
/**
|
||||
* Debug logging system for CNPC+ entities.
|
||||
* Writes to logs/cnpc_debug/[type]_[side].log (file only, no console output).
|
||||
* Toggle at runtime via /cnpcdebugger [type] (client) or /kam debug [type] (server).
|
||||
*/
|
||||
public class CNPCDebug {
|
||||
|
||||
private static final String DIR = "logs/cnpc_debug";
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss");
|
||||
|
||||
// Runtime toggle state per type, separate for client and server
|
||||
private static final Map<String, Boolean> serverToggles = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Boolean> clientToggles = new ConcurrentHashMap<>();
|
||||
|
||||
// Per-type file handlers (lazily initialized)
|
||||
private static final Map<String, Handler> serverHandlers = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Handler> clientHandlers = new ConcurrentHashMap<>();
|
||||
|
||||
// ==================== TOGGLE API ====================
|
||||
|
||||
/**
|
||||
* Toggle server-side debug for given type. Returns new state.
|
||||
*/
|
||||
public static boolean toggleServer(String type) {
|
||||
type = type.toLowerCase();
|
||||
boolean newState = !isServerEnabled(type);
|
||||
serverToggles.put(type, newState);
|
||||
if (newState) {
|
||||
ensureHandler(type, false);
|
||||
}
|
||||
return newState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle client-side debug for given type. Returns new state.
|
||||
*/
|
||||
public static boolean toggleClient(String type) {
|
||||
type = type.toLowerCase();
|
||||
boolean newState = !isClientEnabled(type);
|
||||
clientToggles.put(type, newState);
|
||||
if (newState) {
|
||||
ensureHandler(type, true);
|
||||
}
|
||||
return newState;
|
||||
}
|
||||
|
||||
public static boolean isServerEnabled(String type) {
|
||||
Boolean val = serverToggles.get(type.toLowerCase());
|
||||
return val != null && val;
|
||||
}
|
||||
|
||||
public static boolean isClientEnabled(String type) {
|
||||
Boolean val = clientToggles.get(type.toLowerCase());
|
||||
return val != null && val;
|
||||
}
|
||||
|
||||
// ==================== LOGGING API ====================
|
||||
|
||||
/**
|
||||
* Log a debug message for the given type on the given side.
|
||||
* Callers should guard with isServerEnabled/isClientEnabled for performance.
|
||||
*/
|
||||
public static void log(String type, boolean client, String msg) {
|
||||
type = type.toLowerCase();
|
||||
Map<String, Boolean> toggles = client ? clientToggles : serverToggles;
|
||||
Boolean enabled = toggles.get(type);
|
||||
if (enabled == null || !enabled) return;
|
||||
|
||||
Handler handler = ensureHandler(type, client);
|
||||
if (handler == null) return;
|
||||
|
||||
String side = client ? "CLIENT" : "SERVER";
|
||||
String timestamp;
|
||||
synchronized (DATE_FORMAT) {
|
||||
timestamp = DATE_FORMAT.format(new Date());
|
||||
}
|
||||
String formatted = "[" + timestamp + "][" + side + "][DEBUG-" + type.toUpperCase() + "] " + msg + System.lineSeparator();
|
||||
|
||||
LogRecord record = new LogRecord(Level.INFO, formatted);
|
||||
handler.publish(record);
|
||||
handler.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log on server side.
|
||||
*/
|
||||
public static void logServer(String type, String msg) {
|
||||
log(type, false, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log on client side.
|
||||
*/
|
||||
public static void logClient(String type, String msg) {
|
||||
log(type, true, msg);
|
||||
}
|
||||
|
||||
// ==================== INTERNALS ====================
|
||||
|
||||
private static Handler ensureHandler(String type, boolean client) {
|
||||
Map<String, Handler> handlers = client ? clientHandlers : serverHandlers;
|
||||
Handler existing = handlers.get(type);
|
||||
if (existing != null) return existing;
|
||||
|
||||
synchronized (CNPCDebug.class) {
|
||||
existing = handlers.get(type);
|
||||
if (existing != null) return existing;
|
||||
|
||||
try {
|
||||
File dir = new File(DIR);
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
|
||||
String side = client ? "client" : "server";
|
||||
File file = new File(dir, type + "_" + side + ".log");
|
||||
|
||||
// Rotate old log
|
||||
if (file.exists()) {
|
||||
File backup = new File(dir, type + "_" + side + "-prev.log");
|
||||
if (backup.exists()) backup.delete();
|
||||
file.renameTo(backup);
|
||||
}
|
||||
|
||||
Handler handler = new StreamHandler(new FileOutputStream(file), new Formatter() {
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
return record.getMessage();
|
||||
}
|
||||
});
|
||||
handler.setLevel(Level.ALL);
|
||||
handlers.put(type, handler);
|
||||
|
||||
// Log header
|
||||
String timestamp;
|
||||
synchronized (DATE_FORMAT) {
|
||||
timestamp = DATE_FORMAT.format(new Date());
|
||||
}
|
||||
LogRecord header = new LogRecord(Level.INFO,
|
||||
"[" + timestamp + "][" + (client ? "CLIENT" : "SERVER") + "][DEBUG-" + type.toUpperCase() + "] Debug logging started" + System.lineSeparator());
|
||||
handler.publish(header);
|
||||
handler.flush();
|
||||
|
||||
return handler;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import kamkeel.npcs.controllers.AttributeController;
|
||||
import kamkeel.npcs.controllers.ProfileController;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import kamkeel.npcs.controllers.TelegraphController;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargeTracker;
|
||||
import kamkeel.npcs.controllers.data.profile.CNPCData;
|
||||
import kamkeel.npcs.developer.Developer;
|
||||
import kamkeel.npcs.entity.EntityAbilityBarrier;
|
||||
@@ -37,6 +38,7 @@ import kamkeel.npcs.entity.EntityAbilityPanel;
|
||||
import kamkeel.npcs.entity.EntityAbilitySweeper;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
import kamkeel.npcs.entity.EntityAbilityDome;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergySlicer;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.util.BukkitUtil;
|
||||
@@ -122,7 +124,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta9")
|
||||
@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta10")
|
||||
public class CustomNpcs {
|
||||
|
||||
@SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy")
|
||||
@@ -430,8 +432,9 @@ public class CustomNpcs {
|
||||
AuctionController.Instance.save();
|
||||
}
|
||||
|
||||
kamkeel.npcs.entity.EntityEnergyProjectile.clearAllProjectiles();
|
||||
EntityEnergyProjectile.clearAllProjectiles();
|
||||
EntityAbilityBarrier.clearAllBarriers();
|
||||
EnergyChargeTracker.Instance.clear();
|
||||
|
||||
if (FMLCommonHandler.instance().getSide().isClient())
|
||||
clientJaninoCompiler = null;
|
||||
|
||||
@@ -147,10 +147,7 @@ public class ScriptPlayerEventHandler {
|
||||
EntityPlayerMP mp = (EntityPlayerMP) player;
|
||||
SyncController.syncEffects(mp);
|
||||
SyncController.syncAbilityCooldowns(mp);
|
||||
if (playerData.abilityData.needsSync) {
|
||||
PlayerAbilitySyncPacket.sendToPlayer(mp);
|
||||
playerData.abilityData.needsSync = false;
|
||||
}
|
||||
PlayerAbilitySyncPacket.sendToPlayer(mp);
|
||||
playerData.abilityData.ensureLockStateClear();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargeTracker;
|
||||
import kamkeel.npcs.entity.EntityAbilityBarrier;
|
||||
import kamkeel.npcs.entity.EntityAbilityDome;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
@@ -300,6 +301,7 @@ public class ServerEventsHandler {
|
||||
public void invoke(LivingDeathEvent event) {
|
||||
if (event.entityLiving.worldObj.isRemote)
|
||||
return;
|
||||
EnergyChargeTracker.Instance.removeAllForCaster(event.entityLiving.getEntityId());
|
||||
if (event.source.getEntity() != null) {
|
||||
if (event.source.getEntity() instanceof EntityPlayer) {
|
||||
doExcalibur((EntityPlayer) event.source.getEntity(), event.entityLiving);
|
||||
@@ -535,7 +537,19 @@ public class ServerEventsHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public void playerTracking(PlayerEvent.StartTracking event) {
|
||||
if (!(event.target instanceof EntityPlayerMP || event.target instanceof EntityNPCInterface) || event.target.worldObj.isRemote)
|
||||
if (event.target.worldObj.isRemote) return;
|
||||
|
||||
// Energy charge visuals: send active charges for newly tracked entity
|
||||
if (event.target instanceof EntityLivingBase
|
||||
&& event.entityPlayer instanceof EntityPlayerMP) {
|
||||
EnergyChargeTracker.Instance.sendToPlayer(
|
||||
event.target.getEntityId(),
|
||||
(EntityPlayerMP) event.entityPlayer,
|
||||
(int) event.target.worldObj.getTotalWorldTime()
|
||||
);
|
||||
}
|
||||
|
||||
if (!(event.target instanceof EntityPlayerMP || event.target instanceof EntityNPCInterface))
|
||||
return;
|
||||
|
||||
AnimationData animationData = AnimationData.getData(event.target);
|
||||
|
||||
@@ -25,9 +25,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class FlyPathFinder extends PathFinder {
|
||||
private static final float STRAIGHT_COST = 1.0F;
|
||||
private static final float DIAGONAL_2D_COST = 1.4142135F;
|
||||
private static final float DIAGONAL_3D_COST = 1.7320508F;
|
||||
/**
|
||||
* Used to find obstacles
|
||||
*/
|
||||
@@ -144,7 +141,7 @@ public class FlyPathFinder extends PathFinder {
|
||||
|
||||
private NPCPath addToPath(Entity p_75861_1_, NPCPathPoint p_75861_2_, NPCPathPoint p_75861_3_, NPCPathPoint p_75861_4_, float p_75861_5_) {
|
||||
p_75861_2_.totalPathDistance = 0.0F;
|
||||
p_75861_2_.distanceToNext = this.estimateCostToTarget(p_75861_2_, p_75861_3_);
|
||||
p_75861_2_.distanceToNext = p_75861_2_.distanceToSquared(p_75861_3_);
|
||||
p_75861_2_.distanceToTarget = p_75861_2_.distanceToNext;
|
||||
this.path.clearPath();
|
||||
this.path.addPoint(p_75861_2_);
|
||||
@@ -164,7 +161,7 @@ public class FlyPathFinder extends PathFinder {
|
||||
return this.createEntityPath(p_75861_2_, p_75861_3_);
|
||||
}
|
||||
|
||||
if (this.estimateCostToTarget(pathpoint4, p_75861_3_) < this.estimateCostToTarget(pathpoint3, p_75861_3_)) {
|
||||
if (pathpoint4.distanceToSquared(p_75861_3_) < pathpoint3.distanceToSquared(p_75861_3_)) {
|
||||
pathpoint3 = pathpoint4;
|
||||
}
|
||||
|
||||
@@ -173,13 +170,12 @@ public class FlyPathFinder extends PathFinder {
|
||||
|
||||
for (int j = 0; j < i; ++j) {
|
||||
NPCPathPoint pathpoint5 = this.pathOptions[j];
|
||||
float f1 = pathpoint4.totalPathDistance + this.getTraversalCost(pathpoint4, pathpoint5)
|
||||
+ this.getDirectionalPenalty(pathpoint4, pathpoint5, p_75861_3_);
|
||||
float f1 = pathpoint4.totalPathDistance + pathpoint4.distanceToSquared(pathpoint5);
|
||||
|
||||
if (!pathpoint5.isAssigned() || f1 < pathpoint5.totalPathDistance) {
|
||||
pathpoint5.previous = pathpoint4;
|
||||
pathpoint5.totalPathDistance = f1;
|
||||
pathpoint5.distanceToNext = this.estimateCostToTarget(pathpoint5, p_75861_3_);
|
||||
pathpoint5.distanceToNext = pathpoint5.distanceToSquared(p_75861_3_);
|
||||
|
||||
if (pathpoint5.isAssigned()) {
|
||||
this.path.changeDistance(pathpoint5, pathpoint5.totalPathDistance + pathpoint5.distanceToNext);
|
||||
@@ -198,50 +194,6 @@ public class FlyPathFinder extends PathFinder {
|
||||
}
|
||||
}
|
||||
|
||||
private float getTraversalCost(NPCPathPoint from, NPCPathPoint to) {
|
||||
int dx = Math.abs(to.xCoord - from.xCoord);
|
||||
int dy = Math.abs(to.yCoord - from.yCoord);
|
||||
int dz = Math.abs(to.zCoord - from.zCoord);
|
||||
int changedAxes = dx + dy + dz;
|
||||
if (changedAxes <= 1) {
|
||||
return STRAIGHT_COST;
|
||||
}
|
||||
if (changedAxes == 2) {
|
||||
return DIAGONAL_2D_COST;
|
||||
}
|
||||
return DIAGONAL_3D_COST;
|
||||
}
|
||||
|
||||
private float getDirectionalPenalty(NPCPathPoint from, NPCPathPoint to, NPCPathPoint goal) {
|
||||
int stepX = to.xCoord - from.xCoord;
|
||||
int stepY = to.yCoord - from.yCoord;
|
||||
int stepZ = to.zCoord - from.zCoord;
|
||||
int goalX = goal.xCoord - from.xCoord;
|
||||
int goalY = goal.yCoord - from.yCoord;
|
||||
int goalZ = goal.zCoord - from.zCoord;
|
||||
int stepLen = Math.abs(stepX) + Math.abs(stepY) + Math.abs(stepZ);
|
||||
int goalLen = Math.abs(goalX) + Math.abs(goalY) + Math.abs(goalZ);
|
||||
|
||||
if (stepLen == 0 || goalLen == 0) {
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
float alignment = (float) (stepX * goalX + stepY * goalY + stepZ * goalZ) / (float) (stepLen * goalLen);
|
||||
return (1.0F - alignment) * 0.04F;
|
||||
}
|
||||
|
||||
private float estimateCostToTarget(NPCPathPoint from, NPCPathPoint to) {
|
||||
float dx = Math.abs(to.xCoord - from.xCoord);
|
||||
float dy = Math.abs(to.yCoord - from.yCoord);
|
||||
float dz = Math.abs(to.zCoord - from.zCoord);
|
||||
|
||||
float max = Math.max(dx, Math.max(dy, dz));
|
||||
float min = Math.min(dx, Math.min(dy, dz));
|
||||
float mid = dx + dy + dz - max - min;
|
||||
|
||||
return DIAGONAL_3D_COST * min + DIAGONAL_2D_COST * (mid - min) + STRAIGHT_COST * (max - mid);
|
||||
}
|
||||
|
||||
/**
|
||||
* populates pathOptions with available points and returns the number of options found (args: unused1, currentPoint,
|
||||
* unused2, targetPoint, maxDistance)
|
||||
|
||||
@@ -7,8 +7,6 @@ import noppes.npcs.entity.EntityNPCFlying;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
public class FlyingMoveHelper extends EntityMoveHelper {
|
||||
private static final double CLOSE_ENOUGH_SQ = 0.01D;
|
||||
private static final float MAX_TURN_PER_TICK = 35.0F;
|
||||
private final EntityNPCInterface entity;
|
||||
|
||||
private double posX;
|
||||
@@ -40,24 +38,22 @@ public class FlyingMoveHelper extends EntityMoveHelper {
|
||||
double d5 = MathHelper.sqrt_double(d4);
|
||||
speed = Math.min(d5 / 5.0D, speed);
|
||||
|
||||
if (d4 > CLOSE_ENOUGH_SQ && d5 > 1.0E-4D) {
|
||||
double targetMotionX = speed * (d0 / d5);
|
||||
double targetMotionZ = speed * (d2 / d5);
|
||||
double horizontalLerp = Math.min(1.0D, Math.max(0.12D, speed * 0.6D));
|
||||
this.entity.motionX += (targetMotionX - this.entity.motionX) * horizontalLerp;
|
||||
this.entity.motionZ += (targetMotionZ - this.entity.motionZ) * horizontalLerp;
|
||||
// its here louis!
|
||||
// This is the hurt time that stuns the NPC!!!!!!
|
||||
// if (this.entity.hurtTime == 0 && d4 > 0.5D) {
|
||||
if (d4 >= 0.5D) {
|
||||
this.entity.motionX += (speed * (d0 / d5) - this.entity.motionX) * speed;
|
||||
this.entity.motionZ += (speed * (d2 / d5) - this.entity.motionZ) * speed;
|
||||
|
||||
if (((EntityNPCFlying) this.entity).flyLimitAllow || !this.entity.ais.hasFlyLimit) {
|
||||
double targetMotionY = verticalSpeed * (d1 / d5);
|
||||
if (targetMotionY > 0.0D) {
|
||||
targetMotionY += 0.05D;
|
||||
this.entity.motionY = verticalSpeed * (d1 / d5);
|
||||
if (this.entity.motionY > 0) {
|
||||
this.entity.motionY += 0.1D;
|
||||
}
|
||||
double verticalLerp = Math.min(1.0D, Math.max(0.15D, verticalSpeed * 0.85D));
|
||||
this.entity.motionY += (targetMotionY - this.entity.motionY) * verticalLerp;
|
||||
}
|
||||
|
||||
this.entity.velocityChanged = true;
|
||||
this.entity.rotationYaw = this.limitAngle(this.entity.rotationYaw, (float) ((Math.atan2(-d0, -d2) + Math.PI) * -(180F / Math.PI)), MAX_TURN_PER_TICK);
|
||||
this.entity.rotationYaw = this.limitAngle(this.entity.rotationYaw, (float) ((Math.atan2(-d0, -d2) + Math.PI) * -(180F / Math.PI)), 20.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ import noppes.npcs.entity.EntityNPCInterface;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PathNavigateFlying extends PathNavigate {
|
||||
private static final long MIN_REPATH_INTERVAL_TICKS = 8L;
|
||||
private static final long MAX_REPATH_INTERVAL_TICKS = 30L;
|
||||
private static final int PATH_REUSE_TARGET_DELTA_SQ = 2;
|
||||
private static final int FINAL_NODE_REUSE_DELTA_SQ = 3;
|
||||
|
||||
private EntityNPCInterface theEntity;
|
||||
private World worldObj;
|
||||
@@ -87,21 +83,16 @@ public class PathNavigateFlying extends PathNavigate {
|
||||
}
|
||||
|
||||
public void updatePath() {
|
||||
if (this.targetPos == null) {
|
||||
this.tryUpdatePath = false;
|
||||
return;
|
||||
}
|
||||
|
||||
long now = this.worldObj.getTotalWorldTime();
|
||||
if (now - this.lastTimeUpdated < this.getAdaptiveRepathInterval()) {
|
||||
if (this.worldObj.getTotalWorldTime() - this.lastTimeUpdated > 20L) {
|
||||
if (this.targetPos != null) {
|
||||
this.currentPath = null;
|
||||
this.currentPath = this.getPathToXYZ(this.targetPos.getX(), this.targetPos.getY(), this.targetPos.getZ());
|
||||
this.lastTimeUpdated = this.worldObj.getTotalWorldTime();
|
||||
this.tryUpdatePath = false;
|
||||
}
|
||||
} else {
|
||||
this.tryUpdatePath = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentPath = null;
|
||||
this.currentPath = this.getPathToXYZ(this.targetPos.getX(), this.targetPos.getY(), this.targetPos.getZ());
|
||||
this.lastTimeUpdated = now;
|
||||
this.tryUpdatePath = false;
|
||||
}
|
||||
|
||||
public void setAvoidsWater(boolean p_75491_1_) {
|
||||
@@ -165,8 +156,7 @@ public class PathNavigateFlying extends PathNavigate {
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
if (!this.canNavigate()) {
|
||||
return null;
|
||||
} else if (this.canReuseCurrentPath(pos)) {
|
||||
this.targetPos = pos;
|
||||
} else if (this.currentPath != null && !this.currentPath.isFinished() && pos.equals(this.targetPos)) {
|
||||
return this.currentPath;
|
||||
} else {
|
||||
this.targetPos = pos;
|
||||
@@ -190,8 +180,7 @@ public class PathNavigateFlying extends PathNavigate {
|
||||
return null;
|
||||
} else {
|
||||
BlockPos blockpos = new BlockPos(entity);
|
||||
if (this.canReuseCurrentPath(blockpos)) {
|
||||
this.targetPos = blockpos;
|
||||
if (this.currentPath != null && !this.currentPath.isFinished() && blockpos.equals(this.targetPos)) {
|
||||
return this.currentPath;
|
||||
} else {
|
||||
this.targetPos = blockpos;
|
||||
@@ -226,8 +215,6 @@ public class PathNavigateFlying extends PathNavigate {
|
||||
this.removeSunnyPath();
|
||||
}
|
||||
|
||||
this.smoothCurrentPath();
|
||||
|
||||
if (this.currentPath.getCurrentPathLength() == 0) {
|
||||
return false;
|
||||
} else {
|
||||
@@ -255,72 +242,6 @@ public class PathNavigateFlying extends PathNavigate {
|
||||
return !this.isInLiquid() || (this.canSwim && this.isInLiquid());
|
||||
}
|
||||
|
||||
private long getAdaptiveRepathInterval() {
|
||||
if (this.targetPos == null) {
|
||||
return MAX_REPATH_INTERVAL_TICKS;
|
||||
}
|
||||
double distance = this.theEntity.getDistance(this.targetPos.getX() + 0.5D, this.targetPos.getY(), this.targetPos.getZ() + 0.5D);
|
||||
return (long) MathHelper.clamp_double(distance * 0.6D, MIN_REPATH_INTERVAL_TICKS, MAX_REPATH_INTERVAL_TICKS);
|
||||
}
|
||||
|
||||
private boolean canReuseCurrentPath(BlockPos newTarget) {
|
||||
if (this.currentPath == null || this.currentPath.isFinished() || this.targetPos == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.getBlockDistanceSq(this.targetPos, newTarget) <= PATH_REUSE_TARGET_DELTA_SQ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
NPCPathPoint finalPoint = this.currentPath.getFinalPathPoint();
|
||||
return finalPoint != null && this.getPointDistanceSq(finalPoint, newTarget) <= FINAL_NODE_REUSE_DELTA_SQ;
|
||||
}
|
||||
|
||||
private int getBlockDistanceSq(BlockPos a, BlockPos b) {
|
||||
int dx = a.getX() - b.getX();
|
||||
int dy = a.getY() - b.getY();
|
||||
int dz = a.getZ() - b.getZ();
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
private int getPointDistanceSq(NPCPathPoint point, BlockPos blockPos) {
|
||||
int dx = point.xCoord - blockPos.getX();
|
||||
int dy = point.yCoord - blockPos.getY();
|
||||
int dz = point.zCoord - blockPos.getZ();
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
private void smoothCurrentPath() {
|
||||
if (this.currentPath == null) {
|
||||
return;
|
||||
}
|
||||
int pathLength = this.currentPath.getCurrentPathLength();
|
||||
if (pathLength < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = MathHelper.ceiling_double_int(this.theEntity.width);
|
||||
int height = MathHelper.ceiling_double_int(this.theEntity.height + 1.0F);
|
||||
NPCPathPoint[] points = this.currentPath.points;
|
||||
int writeIndex = 1;
|
||||
int anchorIndex = 0;
|
||||
|
||||
for (int readIndex = 2; readIndex < pathLength; readIndex++) {
|
||||
Vec3 anchor = this.currentPath.getVectorFromIndex(this.theEntity, anchorIndex);
|
||||
Vec3 probe = this.currentPath.getVectorFromIndex(this.theEntity, readIndex);
|
||||
if (!this.isDirectPathBetweenPoints(anchor, probe, width, height)) {
|
||||
points[writeIndex++] = points[readIndex - 1];
|
||||
anchorIndex = readIndex - 1;
|
||||
}
|
||||
}
|
||||
|
||||
points[writeIndex++] = points[pathLength - 1];
|
||||
this.currentPath.setCurrentPathLength(writeIndex);
|
||||
if (this.currentPath.getCurrentPathIndex() >= writeIndex) {
|
||||
this.currentPath.setCurrentPathIndex(Math.max(0, writeIndex - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public void onUpdateNavigation() {
|
||||
|
||||
++this.totalTicks;
|
||||
|
||||
@@ -6,6 +6,7 @@ import cpw.mods.fml.client.registry.RenderingRegistry;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.ObfuscationReflectionHelper;
|
||||
import kamkeel.npcs.addon.client.DBCClient;
|
||||
import kamkeel.npcs.client.command.CommandCNPCDebugger;
|
||||
import kamkeel.npcs.client.renderer.RenderAbilityBeam;
|
||||
import kamkeel.npcs.client.renderer.RenderAbilityDisc;
|
||||
import kamkeel.npcs.client.renderer.RenderAbilityLaser;
|
||||
@@ -53,6 +54,7 @@ import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ReportedException;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.ClientCommandHandler;
|
||||
import net.minecraftforge.client.MinecraftForgeClient;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
@@ -362,6 +364,9 @@ public class ClientProxy extends CommonProxy {
|
||||
ClientTagMapController.Instance = new ClientTagMapController();
|
||||
MinecraftForge.EVENT_BUS.register(new ClientEventHandler());
|
||||
|
||||
// Debug commands
|
||||
ClientCommandHandler.instance.registerCommand(new CommandCNPCDebugger());
|
||||
|
||||
// Telegraph rendering system
|
||||
TelegraphManager.initClient();
|
||||
MinecraftForge.EVENT_BUS.register(new TelegraphRenderer());
|
||||
@@ -419,7 +424,8 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
@Override
|
||||
public PlayerData getPlayerData(EntityPlayer player) {
|
||||
if (player.getUniqueID() == Minecraft.getMinecraft().thePlayer.getUniqueID()) {
|
||||
EntityPlayer local = Minecraft.getMinecraft().thePlayer;
|
||||
if (local != null && player.getUniqueID().equals(local.getUniqueID())) {
|
||||
if (ClientCacheHandler.playerData != null) {
|
||||
if (ClientCacheHandler.playerData.player != player) {
|
||||
ClientCacheHandler.playerData.player = player;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class GuiNpcPather extends GuiNPCInterface implements IGuiData {
|
||||
drawDefaultBackground = false;
|
||||
xSize = 176;
|
||||
title = "Npc Pather";
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
ai = npc.ais;
|
||||
PacketClient.sendClient(new MovingPathGetPacket());
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SubGuiAnimationFrame extends SubGuiInterface implements ITextfieldL
|
||||
|
||||
public SubGuiAnimationFrame(Frame editingFrame) {
|
||||
this.editingFrame = editingFrame;
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,7 @@ public class SubGuiAnimationOptions extends SubGuiInterface {
|
||||
|
||||
public SubGuiAnimationOptions(Animation animation) {
|
||||
this.animation = animation;
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,7 @@ public class SubGuiEditText extends SubGuiInterface {
|
||||
|
||||
public SubGuiEditText(String text) {
|
||||
this.text = text;
|
||||
setBackground("extrasmallbg.png");
|
||||
setBackground("menubg.png");
|
||||
closeOnEsc = true;
|
||||
xSize = 176;
|
||||
ySize = 71;
|
||||
|
||||
@@ -220,7 +220,7 @@ public class GuiFieldBuilder {
|
||||
float fVal = def.getValue() instanceof Number ? ((Number) def.getValue()).floatValue() : 0f;
|
||||
GuiNpcTextField tf = new GuiNpcTextField(widgetId, parent, fontRenderer, fieldX, y, fieldW, 20, String.valueOf(fVal));
|
||||
tf.setFloatsOnly();
|
||||
if (def.hasRange()) tf.setMinMaxDefaultFloat(def.getMin(), def.getMax(), fVal);
|
||||
tf.setMinMaxDefaultFloat(def.getMin(), def.getMax(), fVal);
|
||||
if (!def.isEnabled()) tf.setEnabled(false);
|
||||
if (hover != null) tf.setHoverText(hover);
|
||||
sw.addTextField(tf);
|
||||
@@ -233,7 +233,7 @@ public class GuiFieldBuilder {
|
||||
int iVal = def.getValue() instanceof Number ? ((Number) def.getValue()).intValue() : 0;
|
||||
GuiNpcTextField tf = new GuiNpcTextField(widgetId, parent, fontRenderer, fieldX, y, fieldW, 20, String.valueOf(iVal));
|
||||
tf.setIntegersOnly();
|
||||
if (def.hasRange()) tf.setMinMaxDefault((int) def.getMin(), (int) def.getMax(), iVal);
|
||||
tf.setMinMaxDefault((int) def.getMin(), (int) def.getMax(), iVal);
|
||||
if (!def.isEnabled()) tf.setEnabled(false);
|
||||
if (hover != null) tf.setHoverText(hover);
|
||||
sw.addTextField(tf);
|
||||
|
||||
@@ -411,6 +411,9 @@ public class GuiNPCManageDialogs extends GuiNPCInterface2 implements IScrollData
|
||||
}
|
||||
if (subgui instanceof SubGuiNpcDialog) {
|
||||
saveType(true);
|
||||
if (dialog != null && dialog.id >= 0) {
|
||||
setPrevDialogName(dialog.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -401,6 +401,9 @@ public class GuiNPCManageQuest extends GuiNPCInterface2 implements IScrollData,
|
||||
}
|
||||
if (subgui instanceof SubGuiNpcQuest) {
|
||||
saveType(true);
|
||||
if (quest != null && quest.id >= 0) {
|
||||
setPrevQuestName(quest.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.lwjgl.opengl.GL11;
|
||||
public class GuiQuestCompletion extends GuiNPCInterface implements ITopButtonListener {
|
||||
|
||||
private Quest quest;
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/smallbg.png");
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/menubg.png");
|
||||
|
||||
public GuiQuestCompletion(Quest quest) {
|
||||
super();
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.Vector;
|
||||
|
||||
public class GuiTransportSelection extends GuiNPCInterface implements ITopButtonListener, IScrollData {
|
||||
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/smallbg.png");
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/menubg.png");
|
||||
protected int xSize;
|
||||
protected int guiLeft;
|
||||
protected int guiTop;
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.lwjgl.opengl.GL11;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class GuiScriptGlobal extends GuiNPCInterface implements IGuiData {
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/smallbg.png");
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/menubg.png");
|
||||
private HashMap<Integer, Class<?>> scriptGuiClasses = new HashMap<>();
|
||||
|
||||
public GuiScriptGlobal() {
|
||||
|
||||
@@ -15,7 +15,7 @@ public class SubGuiNpcCooldownPicker extends SubGuiInterface implements ITextfie
|
||||
public SubGuiNpcCooldownPicker(boolean isMCCustom, long currentCooldown) {
|
||||
this.isMCCustom = isMCCustom;
|
||||
this.cooldownValue = currentCooldown;
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
xSize = 220;
|
||||
ySize = 222;
|
||||
closeOnEsc = true;
|
||||
|
||||
@@ -289,7 +289,15 @@ public class AuctionController implements IAuctionHandler {
|
||||
// Global claims are not tied to a single player.
|
||||
claim.playerUUID = null;
|
||||
claim.playerName = "Global";
|
||||
globalClaims.add(claim);
|
||||
synchronized (globalClaims) {
|
||||
// Prevent duplicate claim IDs
|
||||
for (AuctionClaim existing : globalClaims) {
|
||||
if (existing.id.equals(claim.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
globalClaims.add(claim);
|
||||
}
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@@ -305,19 +313,23 @@ public class AuctionController implements IAuctionHandler {
|
||||
|
||||
private AuctionClaim getGlobalClaimInternal(String claimId) {
|
||||
if (claimId == null || claimId.isEmpty()) return null;
|
||||
for (AuctionClaim claim : globalClaims) {
|
||||
if (claim != null && !claim.claimed && claimId.equals(claim.id)) {
|
||||
return claim;
|
||||
synchronized (globalClaims) {
|
||||
for (AuctionClaim claim : globalClaims) {
|
||||
if (claim != null && !claim.claimed && claimId.equals(claim.id)) {
|
||||
return claim;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean claimAndRemoveGlobal(String claimId) {
|
||||
AuctionClaim claim = getGlobalClaimInternal(claimId);
|
||||
if (claim == null) return false;
|
||||
claim.claimed = true;
|
||||
return globalClaims.remove(claim);
|
||||
synchronized (globalClaims) {
|
||||
AuctionClaim claim = getGlobalClaimInternal(claimId);
|
||||
if (claim == null) return false;
|
||||
claim.claimed = true;
|
||||
return globalClaims.remove(claim);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,15 +370,27 @@ public class AuctionController implements IAuctionHandler {
|
||||
return "This item cannot be listed on the Auction House.";
|
||||
}
|
||||
|
||||
// Validate minimum price
|
||||
// Validate prices
|
||||
if (startingPrice <= 0) {
|
||||
return "Starting price must be positive.";
|
||||
}
|
||||
if (startingPrice < ConfigMarket.MinimumListingPrice) {
|
||||
return "Starting price must be at least " + ConfigMarket.MinimumListingPrice + " " + ConfigMarket.CurrencyName + ".";
|
||||
}
|
||||
if (startingPrice > ConfigMarket.MaxBalance) {
|
||||
return "Starting price exceeds maximum allowed value.";
|
||||
}
|
||||
|
||||
// Validate buyout price if set
|
||||
if (buyoutPrice < 0) {
|
||||
return "Buyout price cannot be negative.";
|
||||
}
|
||||
if (buyoutPrice > 0 && buyoutPrice < startingPrice) {
|
||||
return "Buyout price must be higher than starting price.";
|
||||
}
|
||||
if (buyoutPrice > ConfigMarket.MaxBalance) {
|
||||
return "Buyout price exceeds maximum allowed value.";
|
||||
}
|
||||
|
||||
UUID playerUUID = player.getUniqueID();
|
||||
String playerName = player.getCommandSenderName();
|
||||
@@ -386,7 +410,7 @@ public class AuctionController implements IAuctionHandler {
|
||||
|
||||
PlayerTradeData currency = playerData.tradeData;
|
||||
long fee = ConfigMarket.ListingFee;
|
||||
if (!currency.canAfford(fee)) {
|
||||
if (fee > 0 && !currency.canAfford(fee)) {
|
||||
return "You cannot afford the listing fee (" + fee + " " + ConfigMarket.CurrencyName + ").";
|
||||
}
|
||||
|
||||
@@ -400,7 +424,7 @@ public class AuctionController implements IAuctionHandler {
|
||||
}
|
||||
|
||||
// Deduct fee
|
||||
if (!currency.withdraw(fee)) {
|
||||
if (fee > 0 && !currency.withdraw(fee)) {
|
||||
return "Failed to deduct listing fee.";
|
||||
}
|
||||
|
||||
@@ -435,9 +459,16 @@ public class AuctionController implements IAuctionHandler {
|
||||
return "No item provided.";
|
||||
}
|
||||
|
||||
// Validate prices
|
||||
if (startingPrice <= 0) {
|
||||
return "Starting price must be positive.";
|
||||
}
|
||||
if (startingPrice < ConfigMarket.MinimumListingPrice) {
|
||||
return "Starting price must be at least " + ConfigMarket.MinimumListingPrice + " " + ConfigMarket.CurrencyName + ".";
|
||||
}
|
||||
if (buyoutPrice < 0) {
|
||||
return "Buyout price cannot be negative.";
|
||||
}
|
||||
if (buyoutPrice > 0 && buyoutPrice < startingPrice) {
|
||||
return "Buyout price must be higher than starting price.";
|
||||
}
|
||||
@@ -514,7 +545,13 @@ public class AuctionController implements IAuctionHandler {
|
||||
if (penalty > 0) {
|
||||
PlayerData sellerData = PlayerDataController.Instance.getData(listing.sellerUUID);
|
||||
if (sellerData != null) {
|
||||
sellerData.tradeData.withdraw(penalty);
|
||||
if (!sellerData.tradeData.withdraw(penalty)) {
|
||||
// Withdraw whatever they have if they can't cover the full penalty
|
||||
long available = sellerData.tradeData.getBalance();
|
||||
if (available > 0) {
|
||||
sellerData.tradeData.withdraw(available);
|
||||
}
|
||||
}
|
||||
sellerData.save();
|
||||
}
|
||||
}
|
||||
@@ -551,6 +588,11 @@ public class AuctionController implements IAuctionHandler {
|
||||
return "Auction is disabled.";
|
||||
}
|
||||
|
||||
// Validate bid amount
|
||||
if (bidAmount <= 0) {
|
||||
return "Bid amount must be positive.";
|
||||
}
|
||||
|
||||
UUID playerUUID = player.getUniqueID();
|
||||
String playerName = player.getCommandSenderName();
|
||||
|
||||
@@ -641,14 +683,14 @@ public class AuctionController implements IAuctionHandler {
|
||||
listing.highBidderName = playerName;
|
||||
listing.currentBid = bidAmount;
|
||||
listing.bidCount++;
|
||||
|
||||
// Add to new bidder's active bids
|
||||
addToPlayerBids(playerUUID, listing.id);
|
||||
|
||||
// Snipe protection (must be inside sync to prevent race with endAuction)
|
||||
listing.extendForSnipeProtection(ConfigMarket.SnipeProtectionMinutes);
|
||||
}
|
||||
|
||||
// Add to new bidder's active bids
|
||||
addToPlayerBids(playerUUID, listing.id);
|
||||
|
||||
// Snipe protection
|
||||
listing.extendForSnipeProtection(ConfigMarket.SnipeProtectionMinutes);
|
||||
|
||||
markDirty();
|
||||
|
||||
logAuction(EnumAuctionLogAction.BID, playerName, listing.item.getDisplayName(), bidAmount,
|
||||
@@ -834,26 +876,36 @@ public class AuctionController implements IAuctionHandler {
|
||||
}
|
||||
|
||||
public String claimGlobalItem(String claimId, EntityPlayer player) {
|
||||
AuctionClaim claim = getGlobalClaimInternal(claimId);
|
||||
if (claim == null) {
|
||||
return "Global claim not found.";
|
||||
}
|
||||
if (!claim.type.isItem()) {
|
||||
return "This global claim is not an item claim.";
|
||||
}
|
||||
if (claim.item == null) {
|
||||
return "Item data is missing.";
|
||||
}
|
||||
String listingId;
|
||||
String itemName;
|
||||
synchronized (globalClaims) {
|
||||
AuctionClaim claim = getGlobalClaimInternal(claimId);
|
||||
if (claim == null) {
|
||||
return "Global claim not found.";
|
||||
}
|
||||
if (!claim.type.isItem()) {
|
||||
return "This global claim is not an item claim.";
|
||||
}
|
||||
if (claim.item == null) {
|
||||
return "Item data is missing.";
|
||||
}
|
||||
|
||||
if (!player.inventory.addItemStackToInventory(claim.item.copy())) {
|
||||
return "Your inventory is full.";
|
||||
}
|
||||
// Check inventory space before adding (addItemStackToInventory mutates even on partial add)
|
||||
if (!canFitInInventory(player, claim.item)) {
|
||||
return "Not enough inventory space to claim this item.";
|
||||
}
|
||||
|
||||
claimAndRemoveGlobal(claim.id);
|
||||
cleanupListingIfComplete(claim.listingId);
|
||||
// Add to inventory (safe - we verified space above)
|
||||
player.inventory.addItemStackToInventory(claim.item.copy());
|
||||
|
||||
listingId = claim.listingId;
|
||||
itemName = claim.item.getDisplayName();
|
||||
claimAndRemoveGlobal(claim.id);
|
||||
}
|
||||
cleanupListingIfComplete(listingId);
|
||||
markDirty();
|
||||
|
||||
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), claim.item.getDisplayName(), 0,
|
||||
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), itemName, 0,
|
||||
"Global item claim");
|
||||
return null;
|
||||
}
|
||||
@@ -862,19 +914,25 @@ public class AuctionController implements IAuctionHandler {
|
||||
* Global currency claims are acknowledged/cleared and do not alter player balances.
|
||||
*/
|
||||
public String claimGlobalCurrency(String claimId, EntityPlayer player) {
|
||||
AuctionClaim claim = getGlobalClaimInternal(claimId);
|
||||
if (claim == null) {
|
||||
return "Global claim not found.";
|
||||
}
|
||||
if (!claim.type.isCurrency()) {
|
||||
return "This global claim is not a currency claim.";
|
||||
}
|
||||
String listingId;
|
||||
long currency;
|
||||
synchronized (globalClaims) {
|
||||
AuctionClaim claim = getGlobalClaimInternal(claimId);
|
||||
if (claim == null) {
|
||||
return "Global claim not found.";
|
||||
}
|
||||
if (!claim.type.isCurrency()) {
|
||||
return "This global claim is not a currency claim.";
|
||||
}
|
||||
|
||||
claimAndRemoveGlobal(claim.id);
|
||||
cleanupListingIfComplete(claim.listingId);
|
||||
listingId = claim.listingId;
|
||||
currency = claim.currency;
|
||||
claimAndRemoveGlobal(claim.id);
|
||||
}
|
||||
cleanupListingIfComplete(listingId);
|
||||
markDirty();
|
||||
|
||||
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), ConfigMarket.CurrencyName, claim.currency,
|
||||
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), ConfigMarket.CurrencyName, currency,
|
||||
"Global currency claim acknowledged (no balance change)");
|
||||
return null;
|
||||
}
|
||||
@@ -910,11 +968,14 @@ public class AuctionController implements IAuctionHandler {
|
||||
return "Claim was prevented.";
|
||||
}
|
||||
|
||||
// Check inventory space
|
||||
if (!player.inventory.addItemStackToInventory(claim.item.copy())) {
|
||||
return "Your inventory is full.";
|
||||
// Check inventory space before adding (addItemStackToInventory mutates even on partial add)
|
||||
if (!canFitInInventory(player, claim.item)) {
|
||||
return "Not enough inventory space to claim this item.";
|
||||
}
|
||||
|
||||
// Add to inventory (safe - we verified space above)
|
||||
player.inventory.addItemStackToInventory(claim.item.copy());
|
||||
|
||||
// Remove claim from player's auction data
|
||||
playerData.tradeData.claimAndRemove(claimId);
|
||||
playerData.updateClient = true;
|
||||
@@ -973,6 +1034,34 @@ public class AuctionController implements IAuctionHandler {
|
||||
return null; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player's inventory can fit the entire ItemStack without partial adds.
|
||||
* Unlike addItemStackToInventory, this does NOT mutate the inventory.
|
||||
*/
|
||||
private boolean canFitInInventory(EntityPlayer player, ItemStack stack) {
|
||||
int remaining = stack.stackSize;
|
||||
int maxStack = stack.getMaxStackSize();
|
||||
ItemStack[] mainInventory = player.inventory.mainInventory;
|
||||
|
||||
for (int i = 0; i < mainInventory.length; i++) {
|
||||
if (remaining <= 0) return true;
|
||||
|
||||
ItemStack slot = mainInventory[i];
|
||||
if (slot == null) {
|
||||
remaining -= Math.min(remaining, maxStack);
|
||||
} else if (slot.getItem() == stack.getItem()
|
||||
&& slot.getItemDamage() == stack.getItemDamage()
|
||||
&& ItemStack.areItemStackTagsEqual(slot, stack)) {
|
||||
int spaceInSlot = maxStack - slot.stackSize;
|
||||
if (spaceInSlot > 0) {
|
||||
remaining -= Math.min(remaining, spaceInSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return remaining <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up a listing if it's ended and no longer needed.
|
||||
* Now just removes ended listings after some delay since claims are in PlayerData.
|
||||
@@ -1019,6 +1108,7 @@ public class AuctionController implements IAuctionHandler {
|
||||
|
||||
for (AuctionListing listing : listings.values()) {
|
||||
if (listing.status != EnumAuctionStatus.ACTIVE) continue;
|
||||
if (listing.isExpired()) continue;
|
||||
|
||||
// Apply search filter (searches item name AND seller name)
|
||||
if (filter.hasSearchText()) {
|
||||
@@ -1069,6 +1159,7 @@ public class AuctionController implements IAuctionHandler {
|
||||
int count = 0;
|
||||
for (AuctionListing listing : listings.values()) {
|
||||
if (listing.status != EnumAuctionStatus.ACTIVE) continue;
|
||||
if (listing.isExpired()) continue;
|
||||
if (filter.hasSearchText()) {
|
||||
String itemName = listing.item != null ? listing.item.getDisplayName() : "";
|
||||
String sellerName = listing.sellerName != null ? listing.sellerName : "";
|
||||
@@ -1755,20 +1846,12 @@ public class AuctionController implements IAuctionHandler {
|
||||
|
||||
String itemName = listing.item != null ? listing.item.getDisplayName() : "Unknown Item";
|
||||
|
||||
// Return active highest bid.
|
||||
// Return active highest bid - always refund to the actual bidder
|
||||
if (listing.hasBids() && listing.highBidderUUID != null) {
|
||||
AuctionClaim refundClaim;
|
||||
if (cancelCompletely) {
|
||||
refundClaim = AuctionClaim.createRefundClaim(
|
||||
null, "Global", listing.id, listing.currentBid, itemName,
|
||||
listing.highBidderName, null);
|
||||
addGlobalClaim(refundClaim);
|
||||
} else {
|
||||
refundClaim = AuctionClaim.createRefundClaim(
|
||||
listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, itemName,
|
||||
listing.sellerName, null);
|
||||
addClaimToPlayer(listing.highBidderUUID, refundClaim);
|
||||
}
|
||||
AuctionClaim refundClaim = AuctionClaim.createRefundClaim(
|
||||
listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, itemName,
|
||||
listing.sellerName, null);
|
||||
addClaimToPlayer(listing.highBidderUUID, refundClaim);
|
||||
removeFromPlayerBids(listing.highBidderUUID, listing.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import noppes.npcs.CustomNpcs;
|
||||
import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.api.handler.ILinkedItemHandler;
|
||||
import noppes.npcs.api.handler.data.ILinkedItem;
|
||||
import noppes.npcs.api.item.IItemStack;
|
||||
import noppes.npcs.controllers.data.IScriptHandler;
|
||||
@@ -21,7 +22,7 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class LinkedItemController {
|
||||
public class LinkedItemController implements ILinkedItemHandler {
|
||||
private static LinkedItemController Instance;
|
||||
private int lastUsedID = 0;
|
||||
|
||||
@@ -40,25 +41,28 @@ public class LinkedItemController {
|
||||
return Instance;
|
||||
}
|
||||
|
||||
public LinkedItem createItem(String name) {
|
||||
@Override
|
||||
public ILinkedItem createItem(String name) {
|
||||
return new LinkedItem(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IItemStack createItemStack(int id) {
|
||||
LinkedItem linkedItem = this.get(id);
|
||||
LinkedItem linkedItem = (LinkedItem) this.get(id);
|
||||
if (linkedItem != null) {
|
||||
return linkedItem.createStack();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void add(LinkedItem linkedItem) {
|
||||
@Override
|
||||
public void add(ILinkedItem linkedItem) {
|
||||
if (linkedItem != null) {
|
||||
String name = linkedItem.getName();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
int linkedItemId = linkedItem.getId();
|
||||
int id = this.linkedItems.containsKey(linkedItemId) ? linkedItemId : this.getUnusedId();
|
||||
this.linkedItems.put(id, linkedItem);
|
||||
this.linkedItems.put(id, (LinkedItem) linkedItem);
|
||||
linkedItem.setId(id);
|
||||
this.addScript(id);
|
||||
}
|
||||
@@ -69,6 +73,7 @@ public class LinkedItemController {
|
||||
this.linkedItemsScripts.put(id, new LinkedItemScript());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedItem remove(int id) {
|
||||
this.removeScript(id);
|
||||
return this.linkedItems.remove(id);
|
||||
@@ -78,16 +83,19 @@ public class LinkedItemController {
|
||||
this.linkedItemsScripts.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedItem get(int id) {
|
||||
return this.linkedItems.get(id);
|
||||
return (LinkedItem) this.linkedItems.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(int id) {
|
||||
return this.linkedItems.containsKey(id);
|
||||
}
|
||||
|
||||
public boolean contains(LinkedItem linkedItem) {
|
||||
return this.linkedItems.containsValue(linkedItem);
|
||||
@Override
|
||||
public boolean contains(ILinkedItem linkedItem) {
|
||||
return this.linkedItems.containsValue((LinkedItem) linkedItem);
|
||||
}
|
||||
|
||||
public IScriptHandler getScriptHandler(int id) {
|
||||
@@ -219,7 +227,7 @@ public class LinkedItemController {
|
||||
}
|
||||
|
||||
public void delete(int id) {
|
||||
LinkedItem linkedItem = get(id);
|
||||
LinkedItem linkedItem = (LinkedItem) get(id);
|
||||
if (linkedItem != null) {
|
||||
LinkedItem foundItem = remove(id);
|
||||
if (foundItem != null && foundItem.name != null) {
|
||||
|
||||
@@ -91,14 +91,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
*/
|
||||
private transient byte lastSyncedFlags = 0;
|
||||
|
||||
/**
|
||||
* Dirty flag: set when ability data changes (unlock/lock/select/toggle).
|
||||
* Checked by the periodic 10-tick sync handler, which sends an ability
|
||||
* data packet and clears the flag. Avoids scattering sync calls across
|
||||
* every mutation method.
|
||||
*/
|
||||
public transient boolean needsSync = false;
|
||||
|
||||
/**
|
||||
* World time when the last ability was activated (for double-press cancel detection).
|
||||
*/
|
||||
@@ -497,7 +489,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
|
||||
@Override
|
||||
protected void onToggleStateChanged(String key, boolean active, int state) {
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -534,20 +525,17 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
public void setSelectedIndex(int index) {
|
||||
if (index == -1 || (index >= 0 && index < unlockedAbilities.size())) {
|
||||
this.selectedIndex = index;
|
||||
needsSync = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void selectNext() {
|
||||
if (unlockedAbilities.isEmpty()) return;
|
||||
selectedIndex = (selectedIndex + 1) % unlockedAbilities.size();
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
public void selectPrevious() {
|
||||
if (unlockedAbilities.isEmpty()) return;
|
||||
selectedIndex = (selectedIndex - 1 + unlockedAbilities.size()) % unlockedAbilities.size();
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
public String getSelectedAbilityKey() {
|
||||
@@ -576,13 +564,11 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
if (selectedIndex >= unlockedAbilities.size()) {
|
||||
selectedIndex = Math.max(0, unlockedAbilities.size() - 1);
|
||||
}
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
public void unlockAbility(String key) {
|
||||
if (key != null && !key.isEmpty() && !unlockedAbilities.contains(key)) {
|
||||
unlockedAbilities.add(key);
|
||||
needsSync = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,7 +577,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
if (selectedIndex >= unlockedAbilities.size()) {
|
||||
selectedIndex = Math.max(0, unlockedAbilities.size() - 1);
|
||||
}
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
public boolean hasUnlockedAbility(String key) {
|
||||
@@ -837,7 +822,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
|
||||
// Reset sync tracking so next sync sends fresh state
|
||||
lastSyncedFlags = 0;
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -245,8 +245,14 @@ public class PlayerTradeData implements IPlayerTradeData {
|
||||
* Add a claim to this player's pending claims.
|
||||
* Thread-safe - can be called from any thread.
|
||||
*/
|
||||
public void addClaim(AuctionClaim claim) {
|
||||
public synchronized void addClaim(AuctionClaim claim) {
|
||||
if (claim != null && !claim.claimed) {
|
||||
// Prevent duplicate claim IDs
|
||||
for (AuctionClaim existing : claims) {
|
||||
if (existing.id.equals(claim.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
claims.add(claim);
|
||||
}
|
||||
}
|
||||
@@ -276,8 +282,9 @@ public class PlayerTradeData implements IPlayerTradeData {
|
||||
|
||||
/**
|
||||
* Get a specific claim by ID (internal use).
|
||||
* Synchronized to pair with claimAndRemove for thread safety.
|
||||
*/
|
||||
public AuctionClaim getClaimInternal(String claimId) {
|
||||
public synchronized AuctionClaim getClaimInternal(String claimId) {
|
||||
for (AuctionClaim claim : claims) {
|
||||
if (claim.id.equals(claimId) && !claim.claimed) {
|
||||
return claim;
|
||||
@@ -296,8 +303,9 @@ public class PlayerTradeData implements IPlayerTradeData {
|
||||
|
||||
/**
|
||||
* Mark a claim as claimed and remove it.
|
||||
* Synchronized to prevent double-claim from concurrent threads.
|
||||
*/
|
||||
public boolean claimAndRemove(String claimId) {
|
||||
public synchronized boolean claimAndRemove(String claimId) {
|
||||
for (AuctionClaim claim : claims) {
|
||||
if (claim.id.equals(claimId) && !claim.claimed) {
|
||||
claim.claimed = true;
|
||||
|
||||
@@ -91,13 +91,11 @@ public abstract class EntityNPCFlying extends EntityNPCInterface {
|
||||
this.prevLimbSwingAmount = this.limbSwingAmount;
|
||||
double distanceX = this.posX - this.prevPosX;
|
||||
double distanceZ = this.posZ - this.prevPosZ;
|
||||
float distance = MathHelper.sqrt_double(distanceX * distanceX + distanceZ * distanceZ);
|
||||
float swingScale = this.getNavigator().noPath() ? 0.35F : 0.9F;
|
||||
distance *= swingScale;
|
||||
float distance = MathHelper.sqrt_double(distanceX * distanceX + distanceZ * distanceZ) * 4.0F;
|
||||
if (distance > 1.0F) {
|
||||
distance = 1.0F;
|
||||
}
|
||||
this.limbSwingAmount += (distance - this.limbSwingAmount) * 0.18F;
|
||||
this.limbSwingAmount += (distance - this.limbSwingAmount) * 0.4F;
|
||||
this.limbSwing += this.limbSwingAmount;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ItemLinked extends ItemCustomizable {
|
||||
IItemStack itemStack = NpcAPI.Instance().getIItemStack(stack);
|
||||
if (itemStack instanceof ScriptLinkedItem) {
|
||||
ScriptLinkedItem scriptLinkedItem = (ScriptLinkedItem) itemStack;
|
||||
LinkedItem linkedItem = LinkedItemController.getInstance().get(scriptLinkedItem.linkedItem.getId());
|
||||
LinkedItem linkedItem = (LinkedItem) LinkedItemController.getInstance().get(scriptLinkedItem.linkedItem.getId());
|
||||
int prevVersion = scriptLinkedItem.linkedVersion;
|
||||
if (linkedItem != null && scriptLinkedItem.linkedVersion != linkedItem.version) {
|
||||
scriptLinkedItem.linkedItem = linkedItem.clone();
|
||||
|
||||
@@ -71,24 +71,7 @@ import noppes.npcs.api.entity.ICustomNpc;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.api.entity.IPlayer;
|
||||
import noppes.npcs.api.gui.ICustomGui;
|
||||
import noppes.npcs.api.handler.IAbilityHandler;
|
||||
import noppes.npcs.api.handler.IActionManager;
|
||||
import noppes.npcs.api.handler.IAnimationHandler;
|
||||
import noppes.npcs.api.handler.IAttributeHandler;
|
||||
import noppes.npcs.api.handler.IAuctionHandler;
|
||||
import noppes.npcs.api.handler.ICloneHandler;
|
||||
import noppes.npcs.api.handler.ICustomEffectHandler;
|
||||
import noppes.npcs.api.handler.IDialogHandler;
|
||||
import noppes.npcs.api.handler.IFactionHandler;
|
||||
import noppes.npcs.api.handler.IMagicHandler;
|
||||
import noppes.npcs.api.handler.INaturalSpawnsHandler;
|
||||
import noppes.npcs.api.handler.IPartyHandler;
|
||||
import noppes.npcs.api.handler.IProfileHandler;
|
||||
import noppes.npcs.api.handler.IQuestHandler;
|
||||
import noppes.npcs.api.handler.IRecipeHandler;
|
||||
import noppes.npcs.api.handler.IScriptHookHandler;
|
||||
import noppes.npcs.api.handler.ITelegraphHandler;
|
||||
import noppes.npcs.api.handler.ITransportHandler;
|
||||
import noppes.npcs.api.handler.*;
|
||||
import noppes.npcs.api.handler.data.IAnimation;
|
||||
import noppes.npcs.api.handler.data.IFrame;
|
||||
import noppes.npcs.api.handler.data.IFramePart;
|
||||
@@ -99,22 +82,7 @@ import noppes.npcs.compat.PixelmonHelper;
|
||||
import noppes.npcs.config.ConfigScript;
|
||||
import noppes.npcs.constants.EnumAnimationPart;
|
||||
import noppes.npcs.containers.ContainerNpcInterface;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.AuctionController;
|
||||
import noppes.npcs.controllers.ChunkController;
|
||||
import noppes.npcs.controllers.CustomEffectController;
|
||||
import noppes.npcs.controllers.DialogController;
|
||||
import noppes.npcs.controllers.FactionController;
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.PartyController;
|
||||
import noppes.npcs.controllers.QuestController;
|
||||
import noppes.npcs.controllers.RecipeController;
|
||||
import noppes.npcs.controllers.ScriptController;
|
||||
import noppes.npcs.controllers.ScriptEntityData;
|
||||
import noppes.npcs.controllers.ScriptHookController;
|
||||
import noppes.npcs.controllers.ServerCloneController;
|
||||
import noppes.npcs.controllers.SpawnController;
|
||||
import noppes.npcs.controllers.TransportController;
|
||||
import noppes.npcs.controllers.*;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.controllers.data.Frame;
|
||||
import noppes.npcs.controllers.data.FramePart;
|
||||
@@ -383,6 +351,10 @@ public class NpcAPI extends AbstractNpcAPI {
|
||||
return AnimationController.Instance;
|
||||
}
|
||||
|
||||
public ILinkedItemHandler getLinkedItems() {
|
||||
return LinkedItemController.getInstance();
|
||||
}
|
||||
|
||||
public IScriptHookHandler getScriptHooks() {
|
||||
return ScriptHookController.Instance;
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ public class ScriptEnergyLaser<T extends EntityAbilityLaser> extends ScriptEnerg
|
||||
entity.setExpansionSpeed(speed);
|
||||
}
|
||||
|
||||
public int getLingerTicks() {
|
||||
return entity.getLingerTicks();
|
||||
public float getMaxLength() {
|
||||
return entity.getMaxLength();
|
||||
}
|
||||
|
||||
public void setLingerTicks(int ticks) {
|
||||
entity.setLingerTicks(ticks);
|
||||
public void setMaxLength(float maxLength) {
|
||||
entity.setMaxLength(maxLength);
|
||||
}
|
||||
|
||||
public float getCurrentLength() {
|
||||
@@ -89,18 +89,6 @@ public class ScriptEnergyLaser<T extends EntityAbilityLaser> extends ScriptEnerg
|
||||
return entity.getEndZ();
|
||||
}
|
||||
|
||||
public float getLingerAlpha() {
|
||||
return entity.getLingerAlpha();
|
||||
}
|
||||
|
||||
public boolean isLockVerticalDirection() {
|
||||
return entity.isLockVerticalDirection();
|
||||
}
|
||||
|
||||
public void setLockVerticalDirection(boolean lock) {
|
||||
entity.setLockVerticalDirection(lock);
|
||||
}
|
||||
|
||||
// ==================== FIRE ====================
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,7 @@ import noppes.npcs.scripted.NpcAPI;
|
||||
public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
protected final Ability ability;
|
||||
protected final IEntityLivingBase target;
|
||||
private transient IAbility abilityCopy;
|
||||
|
||||
public AbilityEvent(ICustomNpc npc, Ability ability, EntityLivingBase target) {
|
||||
super(npc);
|
||||
@@ -27,7 +28,10 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent {
|
||||
|
||||
@Override
|
||||
public IAbility getAbility() {
|
||||
return ability;
|
||||
if (abilityCopy == null && ability != null) {
|
||||
abilityCopy = ability.deepCopy();
|
||||
}
|
||||
return abilityCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,7 @@ import noppes.npcs.scripted.NpcAPI;
|
||||
public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEvent {
|
||||
protected final Ability ability;
|
||||
protected final IEntityLivingBase target;
|
||||
private transient IAbility abilityCopy;
|
||||
|
||||
public PlayerAbilityEvent(IPlayer player, Ability ability, EntityLivingBase target) {
|
||||
super(player);
|
||||
@@ -27,7 +28,10 @@ public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEve
|
||||
|
||||
@Override
|
||||
public IAbility getAbility() {
|
||||
return ability;
|
||||
if (abilityCopy == null && ability != null) {
|
||||
abilityCopy = ability.deepCopy();
|
||||
}
|
||||
return abilityCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,7 +36,12 @@ public class ScriptDataAbilities implements IDataAbilities {
|
||||
|
||||
@Override
|
||||
public IAbility[] getAbilities() {
|
||||
return data.getAbilities().toArray(new IAbility[0]);
|
||||
List<Ability> live = data.getAbilities();
|
||||
IAbility[] copies = new IAbility[live.size()];
|
||||
for (int i = 0; i < live.size(); i++) {
|
||||
copies[i] = live.get(i).deepCopy();
|
||||
}
|
||||
return copies;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,7 +78,8 @@ public class ScriptDataAbilities implements IDataAbilities {
|
||||
|
||||
@Override
|
||||
public IAbility getAbility(String abilityId) {
|
||||
return data.getAbility(abilityId);
|
||||
Ability a = data.getAbility(abilityId);
|
||||
return a != null ? a.deepCopy() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,6 +121,17 @@ public class ScriptDataAbilities implements IDataAbilities {
|
||||
|
||||
@Override
|
||||
public IAbility getCurrentAbility() {
|
||||
Ability a = data.getCurrentAbility();
|
||||
return a != null ? a.deepCopy() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAbility getSourceAbility(String abilityId) {
|
||||
return data.getAbility(abilityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAbility getSourceCurrentAbility() {
|
||||
return data.getCurrentAbility();
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,8 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData {
|
||||
|
||||
@Override
|
||||
public IAbility getCurrentAbility() {
|
||||
return data.getCurrentAbility();
|
||||
Ability a = data.getCurrentAbility();
|
||||
return a != null ? a.deepCopy() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1693,7 +1693,7 @@ ability.cnpc.effect=Effect
|
||||
ability.cnpc.hazard=Hazard
|
||||
ability.cnpc.trap=Trap
|
||||
ability.cnpc.disc=Disc
|
||||
ability.cnpc.laser_shot=Laser Shot
|
||||
ability.cnpc.laser_shot=Laser
|
||||
ability.cnpc.sweeper=Sweeper
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -1822,6 +1822,7 @@ ability.multiHitDelay=Multi-Hit Delay
|
||||
ability.maxHits=Max Hits
|
||||
ability.distance=Distance
|
||||
ability.maxDistance=Max Distance
|
||||
ability.maxLength=Max Length
|
||||
ability.lifetime=Lifetime
|
||||
ability.stunTicks=Stun Ticks
|
||||
ability.hitWidth=Hit Width
|
||||
@@ -1829,7 +1830,6 @@ ability.hitLength=Hit Length
|
||||
ability.effects=Effects
|
||||
ability.effect.burn=Burn
|
||||
ability.mode=Mode
|
||||
ability.duration=Duration
|
||||
ability.anchorPoint=Anchor Point
|
||||
ability.anchor.offsetX=Offset X
|
||||
ability.anchor.offsetY=Offset Y
|
||||
|
||||
Reference in new issue
Block a user