diff --git a/build.gradle b/build.gradle index 7cc75bcbf..9e652cb1d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id "dts.typescript-generator" } -version = "1.11-beta6.1" +version = "1.11-beta7" group= "kamkeel.customnpc-plus" archivesBaseName = "CustomNPC-Plus" diff --git a/src/api b/src/api index a9c77d20e..658da549e 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit a9c77d20eb83be6a3168480c0d62fa78419e9469 +Subproject commit 658da549eec622ed7bcfb331f655938e8841520a diff --git a/src/main/java/kamkeel/npcs/client/renderer/EnergyChargePreviewRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/EnergyChargePreviewRenderer.java new file mode 100644 index 000000000..66bf4869b --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/EnergyChargePreviewRenderer.java @@ -0,0 +1,38 @@ +package kamkeel.npcs.client.renderer; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; +import kamkeel.npcs.entity.EntityEnergyProjectile; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.client.event.RenderWorldLastEvent; + +/** + * Renders packet-driven charging previews through RenderManager. + */ +public class EnergyChargePreviewRenderer { + + @SubscribeEvent + public void onRenderWorldLast(RenderWorldLastEvent event) { + if (EnergyChargePreviewManager.ClientInstance == null || !EnergyChargePreviewManager.ClientInstance.hasPreviews()) { + return; + } + + Minecraft mc = Minecraft.getMinecraft(); + EntityPlayer player = mc.thePlayer; + if (player == null) return; + + float partialTicks = event.partialTicks; + + for (EntityEnergyProjectile entity : EnergyChargePreviewManager.ClientInstance.getPreviews()) { + if (entity == null || entity.isDead) continue; + + double x = entity.prevPosX + (entity.posX - entity.prevPosX) * partialTicks - RenderManager.renderPosX; + double y = entity.prevPosY + (entity.posY - entity.prevPosY) * partialTicks - RenderManager.renderPosY; + double z = entity.prevPosZ + (entity.posZ - entity.prevPosZ) * partialTicks - RenderManager.renderPosZ; + + RenderManager.instance.renderEntityWithPosYaw(entity, x, y, z, entity.rotationYaw, partialTicks); + } + } +} diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java index 8f87ff331..e9868854a 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java @@ -27,6 +27,9 @@ public class RenderAbilityBeam extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityBeam beam = (EntityAbilityBeam) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java index d013dd306..cfa52a55a 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java @@ -17,6 +17,9 @@ public class RenderAbilityDisc extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityDisc disc = (EntityAbilityDisc) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java index beebe98f3..dcf3e5c06 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDome.java @@ -17,6 +17,9 @@ public class RenderAbilityDome extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityDome dome = (EntityAbilityDome) entity; float radius = dome.getDomeRadius(); float healthPercent = dome.getHealthPercent(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index e38764bfb..155c439b8 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -23,6 +23,9 @@ public class RenderAbilityLaser extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityLaser laser = (EntityAbilityLaser) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java index a3adf71a0..c49ca5f15 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java @@ -19,6 +19,10 @@ public class RenderAbilityOrb extends RenderEnergyAbility { public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { EntityAbilityOrb orb = (EntityAbilityOrb) entity; + if (shouldSkipInitialActiveRender(entity)) { + return; + } + setupRenderState(); GL11.glPushMatrix(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java index 1261aaf1e..d341082b5 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityPanel.java @@ -17,6 +17,9 @@ public class RenderAbilityPanel extends RenderEnergyBarrier { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityAbilityPanel panel = (EntityAbilityPanel) entity; float width = panel.getPanelData().panelWidth; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java index 9496f06a5..ac88c47e8 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyAbility.java @@ -22,11 +22,22 @@ import org.lwjgl.opengl.GL12; public abstract class RenderEnergyAbility extends Render { protected static final ResourceLocation WHITE_TEXTURE = new ResourceLocation("customnpcs", "textures/entity/white.png"); + protected static final int HIDE_INITIAL_ACTIVE_TICKS = 1; public RenderEnergyAbility() { this.shadowSize = 0.0f; } + /** + * Hide the first active tick to avoid the visible spawn-frame pause at fire time. + * Charging previews are never hidden. + */ + protected boolean shouldSkipInitialActiveRender(Entity entity) { + if (!(entity instanceof EntityEnergyAbility)) return false; + EntityEnergyAbility ability = (EntityEnergyAbility) entity; + return !ability.isPreviewMode() && !ability.isCharging() && ability.ticksExisted <= HIDE_INITIAL_ACTIVE_TICKS; + } + /** * Setup GL state for translucent, self-illuminated rendering. * Forces full brightness so world lighting does not affect projectiles. diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java new file mode 100644 index 000000000..850d857a4 --- /dev/null +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyExplosion.java @@ -0,0 +1,183 @@ +package kamkeel.npcs.client.renderer; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.entity.EntityEnergyExplosion; +import net.minecraft.entity.Entity; +import noppes.npcs.config.ConfigClient; +import org.lwjgl.opengl.GL11; + +/** + * Voxel-style renderer for {@link EntityEnergyExplosion}. + * Draws expanding cubic shells with a short fade-out. + */ +@SideOnly(Side.CLIENT) +public class RenderEnergyExplosion extends RenderEnergyAbility { + + private static final float MAX_OUTER_ALPHA = 0.85f; + private static final float MAX_INNER_ALPHA = 1.0f; + private static final float MAX_SMOKE_CUBE_ALPHA = 0.55f; + + @Override + public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) return; + if (!(entity instanceof EntityEnergyExplosion)) return; + if (ConfigClient.LowResExplosion) return; + EntityEnergyExplosion explosion = (EntityEnergyExplosion) entity; + + setupRenderState(); + + GL11.glPushMatrix(); + GL11.glTranslated(x, y, z); + + float radius = explosion.getInterpolatedRadius(partialTicks); + float life = explosion.getLifeProgress(partialTicks); + if (radius > 0.01f && life < 1.0f) { + renderVoxelBurst(explosion, radius, life); + if (explosion.hasLightningEffect()) { + renderAttachedLightning(explosion, 0.55f, Math.max(0.7f, radius * 0.45f)); + } + } + + GL11.glPopMatrix(); + restoreRenderState(); + } + + private void renderVoxelBurst(EntityEnergyExplosion explosion, float radius, float life) { + int outerColor = explosion.getOuterColor(); + int innerColor = explosion.getInnerColor(); + boolean hasOuter = explosion.isOuterColorEnabled(); + + float fade = 1.0f - life; + float outerAlpha = Math.min(MAX_OUTER_ALPHA, (0.25f + fade * 0.75f) * explosion.getOuterColorAlpha()); + float innerAlpha = Math.min(MAX_INNER_ALPHA, (0.30f + fade * 0.80f) * explosion.getInnerAlpha()); + + int steps = Math.max(2, Math.min(6, (int) Math.ceil(radius * 0.65f) + 1)); + float cell = radius / steps; + float cubeHalf = Math.max(0.04f, cell * 0.22f * (0.90f - life * 0.25f)); + long seed = explosion.getRenderSeed(); + + GL11.glDepthMask(false); + renderSmokeVoxelShell(radius, life, seed); + + for (int ix = -steps; ix <= steps; ix++) { + for (int iy = -steps; iy <= steps; iy++) { + for (int iz = -steps; iz <= steps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge != steps) continue; + if (!shouldRenderVoxel(ix, iy, iz, steps, seed, life)) continue; + + float px = ix * cell + jitter(ix, iy, iz, seed, 1) * cell * 0.18f; + float py = iy * cell + jitter(ix, iy, iz, seed, 2) * cell * 0.18f; + float pz = iz * cell + jitter(ix, iy, iz, seed, 3) * cell * 0.18f; + + GL11.glPushMatrix(); + GL11.glTranslatef(px, py, pz); + + if (hasOuter) { + GL11.glPushMatrix(); + float outerScale = 1.0f + explosion.getOuterColorWidth() * 0.35f; + GL11.glScalef(outerScale, outerScale, outerScale); + renderCube(outerColor, outerAlpha * fade, cubeHalf); + GL11.glPopMatrix(); + } + + renderCube(innerColor, innerAlpha * fade, cubeHalf * 0.9f); + GL11.glPopMatrix(); + } + } + } + renderCoreFlash(innerColor, innerAlpha, radius, cubeHalf, life); + GL11.glDepthMask(true); + } + + /** + * Adds large translucent white/gray cubes to fake a smoky volumetric expansion. + */ + private void renderSmokeVoxelShell(float radius, float life, long seed) { + int smokeSteps = Math.max(2, Math.min(5, (int) Math.ceil(radius * 0.5f) + 1)); + float smokeCell = radius / smokeSteps * 1.2f; + float smokeJitter = smokeCell * 0.28f; + float smokeCubeHalf = Math.max(0.07f, smokeCell * 0.24f * (0.95f - life * 0.25f)); + float smokeAlpha = Math.min(MAX_SMOKE_CUBE_ALPHA, (0.15f + (1.0f - life) * 0.55f)); + + for (int ix = -smokeSteps; ix <= smokeSteps; ix++) { + for (int iy = -smokeSteps; iy <= smokeSteps; iy++) { + for (int iz = -smokeSteps; iz <= smokeSteps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge < smokeSteps - 1) continue; + + int h = hash(ix * 5, iy * 7, iz * 11, seed ^ 0x4f1bbcdcL); + if ((h & 3) != 0) continue; + if (life > 0.72f && ((h >>> 5) & 7) > 2) continue; + + float px = ix * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 1) * smokeJitter; + float py = iy * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 2) * smokeJitter; + float pz = iz * smokeCell + jitter(ix, iy, iz, seed ^ 0x2299aa11L, 3) * smokeJitter; + + int smokeColor = ((h >>> 9) & 1) == 0 ? 0xFFFFFF : 0xDCDCDC; + float alpha = smokeAlpha * (0.78f + (((h >>> 10) & 15) / 15.0f) * 0.22f); + + GL11.glPushMatrix(); + GL11.glTranslatef(px, py, pz); + renderCube(smokeColor, alpha, smokeCubeHalf); + GL11.glPopMatrix(); + } + } + } + } + + /** + * White-hot core flash pass for the initial frames. + */ + private void renderCoreFlash(int innerColor, float innerAlpha, float radius, float cubeHalf, float life) { + float flash = saturate((0.60f - life) / 0.60f); + if (flash <= 0.0f) return; + + float coreHalf = Math.max(cubeHalf, radius * 0.07f) * (1.0f + flash * 0.55f); + float whiteAlpha = Math.min(1.0f, 0.25f + flash * 0.95f); + float innerFlashAlpha = Math.min(1.0f, innerAlpha * (0.45f + flash * 0.95f)); + + GL11.glPushMatrix(); + renderCube(0xFFFFFF, whiteAlpha, coreHalf * 1.45f); + GL11.glScalef(0.82f, 0.82f, 0.82f); + renderCube(innerColor, innerFlashAlpha, coreHalf); + GL11.glPopMatrix(); + } + + private float saturate(float value) { + if (value < 0.0f) return 0.0f; + if (value > 1.0f) return 1.0f; + return value; + } + + private boolean shouldRenderVoxel(int ix, int iy, int iz, int steps, long seed, float life) { + int h = hash(ix, iy, iz, seed); + + int keepMask = steps >= 5 ? 3 : 1; + if ((h & keepMask) != 0) return false; + + if (life > 0.55f) { + int decay = (h >>> 3) & 7; + if (decay > 2) return false; + } + + return true; + } + + private float jitter(int ix, int iy, int iz, long seed, int channel) { + int h = hash(ix + channel * 17, iy - channel * 31, iz + channel * 47, seed); + return (((h >>> 8) & 1023) / 1023.0f) - 0.5f; + } + + private int hash(int x, int y, int z, long seed) { + long h = seed; + h ^= (long) x * 73428767L; + h ^= (long) y * 912931L; + h ^= (long) z * 19349663L; + h ^= (h >>> 33); + h *= 0xff51afd7ed558ccdL; + h ^= (h >>> 33); + return (int) h; + } +} diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java index e2aa0868f..be0d75748 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergySlicer.java @@ -17,6 +17,9 @@ public class RenderEnergySlicer extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { + if (shouldSkipInitialActiveRender(entity)) { + return; + } EntityEnergySlicer slicer = (EntityEnergySlicer) entity; float width = slicer.getSliceWidth(); diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index 9be809697..f40c266cb 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -2,8 +2,8 @@ package kamkeel.npcs.command; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayerMP; import noppes.npcs.NoppesUtilServer; diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 55baf60e6..1591c2514 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -1,12 +1,22 @@ package kamkeel.npcs.controllers; -import kamkeel.npcs.controllers.data.ability.*; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.controllers.data.ability.conditions.ConditionHPThreshold; import kamkeel.npcs.controllers.data.ability.conditions.ConditionHitCount; import kamkeel.npcs.controllers.data.ability.conditions.ConditionItem; import kamkeel.npcs.controllers.data.ability.conditions.ConditionHasEffect; import kamkeel.npcs.controllers.data.ability.conditions.ConditionQuestCompleted; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityVariant; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.enums.UserType; +import kamkeel.npcs.controllers.data.ability.extender.IAbilityExtender; +import kamkeel.npcs.controllers.data.ability.gui.IAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.gui.IChainedAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; import kamkeel.npcs.controllers.data.ability.type.AbilityDash; @@ -14,7 +24,6 @@ import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.ability.type.AbilityHazard; import kamkeel.npcs.controllers.data.ability.type.AbilityEffect; import kamkeel.npcs.controllers.data.ability.type.AbilityHeavyHit; -import kamkeel.npcs.controllers.data.ability.type.AbilityProjectile; import kamkeel.npcs.controllers.data.ability.type.AbilityShockwave; import kamkeel.npcs.controllers.data.ability.type.AbilitySlam; import kamkeel.npcs.controllers.data.ability.type.AbilityTeleport; @@ -26,7 +35,6 @@ 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.AbilityOrb; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityShield; -import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySlicer; import kamkeel.npcs.controllers.data.ability.type.energy.AbilitySweeper; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityWall; import net.minecraft.entity.EntityLivingBase; @@ -38,6 +46,7 @@ import noppes.npcs.api.handler.IAbilityHandler; import noppes.npcs.controllers.PlayerDataController; import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.util.NBTJsonUtil; +import kamkeel.npcs.util.FileNameHelper; import java.io.File; import java.util.*; @@ -206,6 +215,7 @@ public class AbilityController implements IAbilityHandler { customAbilities.clear(); customAbilitiesById.clear(); int migrated = 0; + Map migratedAbilityNames = new HashMap(); File dir = getDir(); File[] files = dir.exists() ? dir.listFiles() : null; @@ -214,7 +224,7 @@ public class AbilityController implements IAbilityHandler { if (!file.isFile() || !file.getName().endsWith(".json")) continue; try { String filename = file.getName(); - String key = filename.substring(0, filename.length() - 5); + String fileKey = filename.substring(0, filename.length() - 5); NBTTagCompound nbt = NBTJsonUtil.LoadFile(file); Ability ability = fromNBT(nbt); @@ -227,48 +237,55 @@ public class AbilityController implements IAbilityHandler { continue; } - // Legacy migration: UUID-named files get renamed to name-based - if (UUID_PATTERN.matcher(key).matches()) { - String name = ability.getName(); - if (name == null || name.isEmpty()) { - name = key; // Fallback to UUID if no name stored - } + String storedName = ability.getName(); + String preferredName; + if (UUID_PATTERN.matcher(fileKey).matches()) { + preferredName = (storedName != null && !storedName.isEmpty()) ? storedName : fileKey; + } else { + preferredName = fileKey; + } + String name = FileNameHelper.sanitizeName(preferredName, "Ability"); + name = makeUniqueNameForLoad(customAbilities, dir, file, name, fileKey); - // Ensure unique name - while (hasCustomAbilityName(name) || nameFileExists(dir, name)) { - name = name + "_"; - } - ability.setName(name); - - // Rename file from UUID.json to name.json - File newFile = new File(dir, name + ".json"); - if (file.renameTo(newFile)) { - LogWriter.info("Migrated ability file: " + key + ".json -> " + name + ".json"); + boolean dirty = false; + if (!name.equals(fileKey)) { + File renamedFile = new File(dir, name + ".json"); + if (file.renameTo(renamedFile)) { + LogWriter.info("Migrated ability file: " + fileKey + ".json -> " + name + ".json"); + file = renamedFile; + migrated++; + dirty = true; } else { - LogWriter.error("Failed to rename ability file: " + key + ".json -> " + name + ".json"); + LogWriter.error("Failed to rename ability file: " + fileKey + ".json -> " + name + ".json"); } + } - key = name; - migrated++; + ability.setName(name); + if (storedName != null && !storedName.equals(name)) { + dirty = true; + if (!storedName.isEmpty()) { + migratedAbilityNames.put(storedName, name); + } + } + if (!fileKey.equals(name)) { + migratedAbilityNames.put(fileKey, name); } // Ensure ability has a UUID; generate one for legacy abilities String uuid = ability.getId(); - if (uuid == null || uuid.isEmpty() || uuid.equals(key)) { + if (uuid == null || uuid.isEmpty() || uuid.equals(fileKey) || uuid.equals(name)) { // Legacy: id was the name or empty — generate a real UUID uuid = UUID.randomUUID().toString(); ability.setId(uuid); - // Re-save file with the new UUID - try { - NBTJsonUtil.SaveFile(file, ability.writeNBT()); - } catch (Exception ex) { - LogWriter.error("Failed to save UUID for ability: " + key, ex); - } migrated++; + dirty = true; } - ability.setName(key); - customAbilities.put(key, ability); + if (dirty) { + NBTJsonUtil.SaveFile(file, ability.writeNBT()); + } + + customAbilities.put(name, ability); customAbilitiesById.put(uuid, ability); } catch (Exception e) { LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e); @@ -278,23 +295,68 @@ public class AbilityController implements IAbilityHandler { customAbilityRevision++; if (migrated > 0) { - LogWriter.info("Migrated " + migrated + " abilities (UUID-named files or missing UUIDs)"); + LogWriter.info("Migrated " + migrated + " abilities (filename normalization / UUID repair)"); } LogWriter.info("Loaded " + customAbilities.size() + " custom abilities"); - loadChainedAbilities(); + loadChainedAbilities(migratedAbilityNames); } - private boolean nameFileExists(File dir, String name) { - return new File(dir, name + ".json").exists(); + private boolean nameFileExists(File dir, String name, File ignoredFile) { + File existing = new File(dir, name + ".json"); + if (!existing.exists()) return false; + return ignoredFile == null || !existing.getAbsolutePath().equals(ignoredFile.getAbsolutePath()); + } + + private String makeUniqueNameForLoad(Map loadedMap, File dir, File currentFile, String baseName, String selfKey) { + String candidate = baseName; + while ((loadedMap.containsKey(candidate) && !candidate.equals(selfKey)) + || nameFileExists(dir, candidate, currentFile)) { + candidate = candidate + "_"; + } + return candidate; + } + + private String makeUniqueNameForMap(Map map, String baseName, String selfName) { + String candidate = baseName; + while (map.containsKey(candidate) && (selfName == null || !candidate.equals(selfName))) { + candidate = candidate + "_"; + } + return candidate; + } + + private boolean remapChainEntryReferences(ChainedAbility chain, Map migratedAbilityNames) { + if (chain == null || migratedAbilityNames == null || migratedAbilityNames.isEmpty()) return false; + boolean changed = false; + for (ChainedAbilityEntry entry : chain.getEntries()) { + if (entry == null || !entry.isReference()) continue; + String ref = entry.getAbilityReference(); + if (ref == null || ref.isEmpty()) continue; + + String mapped = migratedAbilityNames.get(ref); + if (mapped == null) { + for (Map.Entry e : migratedAbilityNames.entrySet()) { + if (e.getKey().equalsIgnoreCase(ref)) { + mapped = e.getValue(); + break; + } + } + } + + if (mapped != null && !mapped.equals(ref)) { + entry.setAbilityReference(mapped); + changed = true; + } + } + return changed; } public boolean saveCustomAbility(Ability ability) { if (ability == null) return false; if (ability.isBuiltIn()) return false; - String name = ability.getName(); - if (name == null || name.isEmpty()) return false; + String name = FileNameHelper.sanitizeName(ability.getName(), "Ability"); + ability.setName(name); String uuid = ability.getId(); boolean isNew = (uuid == null || uuid.isEmpty()); @@ -305,26 +367,17 @@ public class AbilityController implements IAbilityHandler { ability.setId(uuid); // Ensure unique name - while (hasCustomAbilityName(name)) { - name = name + "_"; - } + name = makeUniqueNameForMap(customAbilities, name, null); ability.setName(name); } else { // Existing ability — check for rename via UUID lookup Ability existing = customAbilitiesById.get(uuid); if (existing != null) { String oldName = existing.getName(); - if (!oldName.equals(name)) { - // Name changed! Ensure new name is unique (excluding self) - String testName = name; - while (customAbilities.containsKey(testName) && !testName.equals(oldName)) { - testName = testName + "_"; - } - if (!testName.equals(name)) { - name = testName; - ability.setName(name); - } + name = makeUniqueNameForMap(customAbilities, name, oldName); + ability.setName(name); + if (!oldName.equals(name)) { // Delete old file and remove old map entry File oldFile = new File(getDir(), oldName + ".json"); if (oldFile.exists()) { @@ -332,6 +385,9 @@ public class AbilityController implements IAbilityHandler { } customAbilities.remove(oldName); } + } else { + name = makeUniqueNameForMap(customAbilities, name, null); + ability.setName(name); } } @@ -367,6 +423,15 @@ public class AbilityController implements IAbilityHandler { if (name == null || name.isEmpty()) return false; Ability removed = customAbilities.remove(name); + if (removed == null) { + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + removed = customAbilities.remove(sanitized); + if (removed != null) { + name = sanitized; + } + } + } if (removed == null) return false; // Also remove from UUID index @@ -405,7 +470,13 @@ public class AbilityController implements IAbilityHandler { } public Ability getCustomAbility(String name) { - return customAbilities.get(name); + Ability ability = customAbilities.get(name); + if (ability != null) return ability; + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + return customAbilities.get(sanitized); + } + return null; } public Set getCustomAbilityNames() { @@ -445,9 +516,10 @@ public class AbilityController implements IAbilityHandler { // CHAINED ABILITIES // ═══════════════════════════════════════════════════════════════════════════ - private void loadChainedAbilities() { + private void loadChainedAbilities(Map migratedAbilityNames) { chainedAbilities.clear(); chainedAbilitiesById.clear(); + int migrated = 0; File dir = getChainedDir(); File[] files = dir.exists() ? dir.listFiles() : null; @@ -456,19 +528,51 @@ public class AbilityController implements IAbilityHandler { if (!file.isFile() || !file.getName().endsWith(".json")) continue; try { String filename = file.getName(); - String key = filename.substring(0, filename.length() - 5); + String fileKey = filename.substring(0, filename.length() - 5); NBTTagCompound nbt = NBTJsonUtil.LoadFile(file); ChainedAbility chain = new ChainedAbility(); chain.readNBT(nbt); - chain.setName(key); - chainedAbilities.put(key, chain); + String storedName = chain.getName(); + String name = FileNameHelper.sanitizeName(fileKey, "Chain"); + name = makeUniqueNameForLoad(chainedAbilities, dir, file, name, fileKey); + + boolean dirty = false; + if (!name.equals(fileKey)) { + File renamedFile = new File(dir, name + ".json"); + if (file.renameTo(renamedFile)) { + LogWriter.info("Migrated chained ability file: " + fileKey + ".json -> " + name + ".json"); + file = renamedFile; + dirty = true; + } else { + LogWriter.error("Failed to rename chained ability file: " + fileKey + ".json -> " + name + ".json"); + } + } + + chain.setName(name); + if (storedName != null && !storedName.equals(name)) { + dirty = true; + } + + if (remapChainEntryReferences(chain, migratedAbilityNames)) { + dirty = true; + } String uuid = chain.getId(); - if (uuid != null && !uuid.isEmpty()) { - chainedAbilitiesById.put(uuid, chain); + if (uuid == null || uuid.isEmpty() || uuid.equals(fileKey) || uuid.equals(name)) { + uuid = UUID.randomUUID().toString(); + chain.setId(uuid); + dirty = true; } + + if (dirty) { + NBTJsonUtil.SaveFile(file, chain.writeNBT()); + migrated++; + } + + chainedAbilities.put(name, chain); + chainedAbilitiesById.put(uuid, chain); } catch (Exception e) { LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e); } @@ -476,14 +580,17 @@ public class AbilityController implements IAbilityHandler { } chainedAbilityRevision++; + if (migrated > 0) { + LogWriter.info("Migrated " + migrated + " chained abilities"); + } LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities"); } public boolean saveChainedAbility(ChainedAbility chain) { if (chain == null) return false; - String name = chain.getName(); - if (name == null || name.isEmpty()) return false; + String name = FileNameHelper.sanitizeName(chain.getName(), "Chain"); + chain.setName(name); String uuid = chain.getId(); boolean isNew = (uuid == null || uuid.isEmpty()); @@ -492,30 +599,25 @@ public class AbilityController implements IAbilityHandler { uuid = UUID.randomUUID().toString(); chain.setId(uuid); - while (hasChainedAbilityName(name)) { - name = name + "_"; - } + name = makeUniqueNameForMap(chainedAbilities, name, null); chain.setName(name); } else { ChainedAbility existing = chainedAbilitiesById.get(uuid); if (existing != null) { String oldName = existing.getName(); - if (!oldName.equals(name)) { - String testName = name; - while (chainedAbilities.containsKey(testName) && !testName.equals(oldName)) { - testName = testName + "_"; - } - if (!testName.equals(name)) { - name = testName; - chain.setName(name); - } + name = makeUniqueNameForMap(chainedAbilities, name, oldName); + chain.setName(name); + if (!oldName.equals(name)) { File oldFile = new File(getChainedDir(), oldName + ".json"); if (oldFile.exists()) { oldFile.delete(); } chainedAbilities.remove(oldName); } + } else { + name = makeUniqueNameForMap(chainedAbilities, name, null); + chain.setName(name); } } @@ -551,6 +653,15 @@ public class AbilityController implements IAbilityHandler { if (name == null || name.isEmpty()) return false; ChainedAbility removed = chainedAbilities.remove(name); + if (removed == null) { + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + removed = chainedAbilities.remove(sanitized); + if (removed != null) { + name = sanitized; + } + } + } if (removed == null) return false; String uuid = removed.getId(); @@ -601,6 +712,12 @@ public class AbilityController implements IAbilityHandler { } } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key)) { + ChainedAbility sanitized = chainedAbilities.get(sanitizedKey); + if (sanitized != null) return sanitized.deepCopy(); + } + return null; } @@ -611,12 +728,20 @@ public class AbilityController implements IAbilityHandler { for (String name : chainedAbilities.keySet()) { if (name.equalsIgnoreCase(key)) return true; } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key) && chainedAbilities.containsKey(sanitizedKey)) return true; return false; } @Override public ChainedAbility getChainedAbility(String name) { ChainedAbility chain = chainedAbilities.get(name); + if (chain == null) { + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + chain = chainedAbilities.get(sanitized); + } + } return chain != null ? chain.deepCopy() : null; } @@ -639,7 +764,9 @@ public class AbilityController implements IAbilityHandler { @Override public boolean hasChainedAbilityName(String name) { - return chainedAbilities.containsKey(name); + if (chainedAbilities.containsKey(name)) return true; + String sanitized = FileNameHelper.sanitizeTextInput(name); + return !sanitized.equals(name) && chainedAbilities.containsKey(sanitized); } @Override @@ -727,6 +854,12 @@ public class AbilityController implements IAbilityHandler { } } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key)) { + Ability sanitized = customAbilities.get(sanitizedKey); + if (sanitized != null) return sanitized.deepCopy(); + } + return null; } @@ -771,8 +904,14 @@ public class AbilityController implements IAbilityHandler { } public boolean hasAbility(String key) { - return builtAbilities.containsKey(key) || customAbilities.containsKey(key) - || customAbilitiesById.containsKey(key); + if (builtAbilities.containsKey(key) || customAbilities.containsKey(key) + || customAbilitiesById.containsKey(key)) { + return true; + } + String sanitized = FileNameHelper.sanitizeTextInput(key); + return !sanitized.equals(key) + && (builtAbilities.containsKey(sanitized) || customAbilities.containsKey(sanitized) + || customAbilitiesById.containsKey(sanitized)); } public Ability getCustomAbilityByUUID(String uuid) { @@ -780,7 +919,13 @@ public class AbilityController implements IAbilityHandler { } public Ability getCustomAbilityByName(String name) { - return customAbilities.get(name); + Ability ability = customAbilities.get(name); + if (ability != null) return ability; + String sanitized = FileNameHelper.sanitizeTextInput(name); + if (!sanitized.equals(name)) { + return customAbilities.get(sanitized); + } + return null; } /** @@ -814,6 +959,9 @@ public class AbilityController implements IAbilityHandler { if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) return true; } + String sanitizedKey = FileNameHelper.sanitizeTextInput(key); + if (!sanitizedKey.equals(key) && customAbilities.containsKey(sanitizedKey)) return true; + return false; } @@ -1121,7 +1269,9 @@ public class AbilityController implements IAbilityHandler { @Override public boolean hasCustomAbilityName(String name) { - return customAbilities.containsKey(name); + if (customAbilities.containsKey(name)) return true; + String sanitized = FileNameHelper.sanitizeTextInput(name); + return !sanitized.equals(name) && customAbilities.containsKey(sanitized); } @Override diff --git a/src/main/java/kamkeel/npcs/controllers/ProfileController.java b/src/main/java/kamkeel/npcs/controllers/ProfileController.java index 103da1309..f7ac06bb1 100644 --- a/src/main/java/kamkeel/npcs/controllers/ProfileController.java +++ b/src/main/java/kamkeel/npcs/controllers/ProfileController.java @@ -832,15 +832,6 @@ public class ProfileController implements IProfileHandler { else compound = (NBTTagCompound) compound.copy(); playerData.setNBT(compound); - - // Trade data is shared across all profile slots - copy from player's live data - PlayerData liveData = PlayerData.get(player); - if (liveData != null) { - NBTTagCompound tradeCompound = new NBTTagCompound(); - liveData.tradeData.writeToNBT(tradeCompound); - playerData.tradeData.readFromNBT(tradeCompound); - } - return playerData; } diff --git a/src/main/java/kamkeel/npcs/controllers/SyncController.java b/src/main/java/kamkeel/npcs/controllers/SyncController.java index af7366a3f..f27428807 100644 --- a/src/main/java/kamkeel/npcs/controllers/SyncController.java +++ b/src/main/java/kamkeel/npcs/controllers/SyncController.java @@ -6,7 +6,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import kamkeel.npcs.addon.DBCAddon; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.LargeAbstractPacket; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSyncAction; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 9d61188c5..4a8558218 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -3,6 +3,18 @@ package kamkeel.npcs.controllers.data.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.data.AbilityIconData; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.enums.InvulnerableMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.preview.PreviewEntityHandler; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.gui.IAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.enums.RotationMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -10,6 +22,7 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityDome; import kamkeel.npcs.entity.EntityAbilityPanel; +import kamkeel.npcs.util.FileNameHelper; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -74,11 +87,12 @@ public abstract class Ability implements IAbility, IAbilityAction { // Interruption protected boolean interruptible = true; + protected InvulnerableMode invulnerableMode = InvulnerableMode.NONE; // Feedback - protected LockMovementType lockMovement = LockMovementType.WINDUP; + protected LockMode lockMovement = LockMode.WINDUP; protected RotationMode rotationMode = RotationMode.FREE; - protected LockMovementType rotationPhase = LockMovementType.WINDUP_AND_ACTIVE; + protected LockMode rotationPhase = LockMode.WINDUP_AND_ACTIVE; protected int windUpColor = 0x80FF4400; // Telegraph color during wind up protected int activeColor = 0xC0FF0000; // Telegraph warning/active color @@ -637,10 +651,10 @@ public abstract class Ability implements IAbility, IAbilityAction { .tab("General").hover("ability.hover.freeOnCast")); } defs.add(FieldDef.section("ability.section.movement").tab("General")); - defs.add(FieldDef.stringEnumField("ability.lockMovement", LockMovementType.getDisplayKeys(), + defs.add(FieldDef.stringEnumField("ability.lockMovement", LockMode.getDisplayKeys(), () -> this.getLockMovement().getDisplayKey(), v -> { - for (LockMovementType t : LockMovementType.values()) { + for (LockMode t : LockMode.values()) { if (t.getDisplayKey().equals(v)) { this.setLockMovement(t); break; @@ -664,7 +678,7 @@ public abstract class Ability implements IAbility, IAbilityAction { FieldDef.stringEnumField("ability.rotationPhase", getRotationPhaseKeys(), () -> this.getRotationPhase().getDisplayKey(), v -> { - for (LockMovementType t : LockMovementType.values()) { + for (LockMode t : LockMode.values()) { if (t.getDisplayKey().equals(v)) { this.setRotationPhase(t); break; @@ -676,10 +690,24 @@ public abstract class Ability implements IAbility, IAbilityAction { ).tab("General")); defs.add(FieldDef.row( FieldDef.boolField("ability.interruptible", this::isInterruptible, this::setInterruptible) - .hover("ability.hover.interruptible"), + .hover("ability.hover.interruptible") + .enabledWhen(() -> !invulnerableMode.invulnerableDuringWindup()), FieldDef.intField("ability.dazedTicks", this::getDazedTicks, this::setDazedTicks) - .range(0, 1000).visibleWhen(this::isInterruptible) + .range(0, 1000) + .visibleWhen(() -> isInterruptible() && !invulnerableMode.invulnerableDuringWindup()) ).tab("General")); + defs.add(FieldDef.stringEnumField("ability.invulnerable", InvulnerableMode.getDisplayKeys(), + () -> this.getInvulnerableMode().getDisplayKey(), + v -> { + for (InvulnerableMode mode : InvulnerableMode.values()) { + if (mode.getDisplayKey().equals(v)) { + this.setInvulnerableMode(mode); + break; + } + } + }) + .hover("ability.hover.invulnerable") + .tab("General")); // ── Burst section ──────────────────────────────────────────── if (allowBurst()) { @@ -1163,6 +1191,9 @@ public abstract class Ability implements IAbility, IAbilityAction { if (!interruptible || (phase != AbilityPhase.WINDUP && phase != AbilityPhase.BURST_DELAY)) { return false; } + if (isInvulnerableForCurrentPhase()) { + return false; + } // Only direct physical hits can interrupt, not magic, fire, or other indirect damage if (source == null) { @@ -1321,6 +1352,7 @@ public abstract class Ability implements IAbility, IAbilityAction { nbt.setBoolean("syncWindup", syncWindupWithAnimation); nbt.setInteger("recovery", dazedTicks); nbt.setBoolean("interruptible", interruptible); + nbt.setInteger("invulnerableMode", invulnerableMode.ordinal()); nbt.setInteger("lockMovement", lockMovement.ordinal()); nbt.setInteger("rotationMode", rotationMode.ordinal()); nbt.setInteger("rotationPhase", rotationPhase.ordinal()); @@ -1400,9 +1432,12 @@ public abstract class Ability implements IAbility, IAbilityAction { syncWindupWithAnimation = nbt.hasKey("syncWindup") ? nbt.getBoolean("syncWindup") : true; dazedTicks = nbt.hasKey("recovery") ? nbt.getInteger("recovery") : 80; interruptible = nbt.hasKey("interruptible") ? nbt.getBoolean("interruptible") : true; - lockMovement = LockMovementType.fromOrdinal(nbt.getInteger("lockMovement")); + invulnerableMode = nbt.hasKey("invulnerableMode") + ? InvulnerableMode.fromOrdinal(nbt.getInteger("invulnerableMode")) + : InvulnerableMode.NONE; + lockMovement = LockMode.fromOrdinal(nbt.getInteger("lockMovement")); rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode")); - rotationPhase = LockMovementType.fromOrdinal(nbt.getInteger("rotationPhase")); + rotationPhase = LockMode.fromOrdinal(nbt.getInteger("rotationPhase")); windUpColor = nbt.getInteger("windUpColor"); activeColor = nbt.getInteger("activeColor"); windUpSound = nbt.getString("windUpSound"); @@ -1490,7 +1525,12 @@ public abstract class Ability implements IAbility, IAbilityAction { } public void setName(String name) { - this.name = name; + if (builtIn) { + this.name = name != null ? name : ""; + return; + } + String fallback = (this.name != null && !this.name.isEmpty()) ? this.name : "Ability"; + this.name = FileNameHelper.sanitizeName(name, fallback); } /** @@ -1499,7 +1539,9 @@ public abstract class Ability implements IAbility, IAbilityAction { * Converts & color codes to § for rendering. */ public String getDisplayName() { - String result = (displayName != null && !displayName.isEmpty()) ? displayName : name; + String result = (displayName != null && !displayName.isEmpty()) + ? displayName + : FileNameHelper.toDisplayName(name); return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : ""; } @@ -1653,11 +1695,27 @@ public abstract class Ability implements IAbility, IAbilityAction { this.interruptible = interruptible; } - public LockMovementType getLockMovement() { + public InvulnerableMode getInvulnerableMode() { + return invulnerableMode; + } + + public void setInvulnerableMode(InvulnerableMode invulnerableMode) { + this.invulnerableMode = invulnerableMode != null ? invulnerableMode : InvulnerableMode.NONE; + } + + public boolean isInvulnerableDuringWindup() { + return invulnerableMode.invulnerableDuringWindup(); + } + + public boolean isInvulnerableDuringActive() { + return invulnerableMode.invulnerableDuringActive(); + } + + public LockMode getLockMovement() { return lockMovement; } - public void setLockMovement(LockMovementType lockMovement) { + public void setLockMovement(LockMode lockMovement) { this.lockMovement = lockMovement; } @@ -1669,11 +1727,11 @@ public abstract class Ability implements IAbility, IAbilityAction { this.rotationMode = rotationMode; } - public LockMovementType getRotationPhase() { + public LockMode getRotationPhase() { return rotationPhase; } - public void setRotationPhase(LockMovementType rotationPhase) { + public void setRotationPhase(LockMode rotationPhase) { this.rotationPhase = rotationPhase; } @@ -1694,7 +1752,7 @@ public abstract class Ability implements IAbility, IAbilityAction { */ @Override public void setLockMovementType(int type) { - this.lockMovement = LockMovementType.fromOrdinal(type); + this.lockMovement = LockMode.fromOrdinal(type); } /** @@ -1734,7 +1792,7 @@ public abstract class Ability implements IAbility, IAbilityAction { */ @Override public void setRotationPhaseType(int type) { - this.rotationPhase = LockMovementType.fromOrdinal(type); + this.rotationPhase = LockMode.fromOrdinal(type); } /** @@ -1785,6 +1843,20 @@ public abstract class Ability implements IAbility, IAbilityAction { } } + /** + * Check if this ability grants invulnerability during its current execution phase. + */ + public boolean isInvulnerableForCurrentPhase() { + return invulnerableMode.isInvulnerableInPhase(phase); + } + + /** + * Check if this ability grants invulnerability for a specific phase. + */ + public boolean isInvulnerableForPhase(AbilityPhase phase) { + return invulnerableMode.isInvulnerableInPhase(phase); + } + /** * Check if rotation should be locked during the current phase. */ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java index 60a64369d..aadca44a3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityAction.java @@ -1,6 +1,8 @@ package kamkeel.npcs.controllers.data.ability; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import net.minecraft.nbt.NBTTagCompound; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java index cdfead9c0..af9e31ba0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/ConcurrentSlot.java @@ -1,5 +1,6 @@ package kamkeel.npcs.controllers.data.ability; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import net.minecraft.entity.EntityLivingBase; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/LockType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/LockType.java deleted file mode 100644 index 904a6c876..000000000 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/LockType.java +++ /dev/null @@ -1,47 +0,0 @@ -package kamkeel.npcs.controllers.data.ability; - -/** - * Defines what an ability locks when Lock Position is enabled. - */ -public enum LockType { - MOVEMENT, - ROTATION, - BOTH; - - public boolean locksMovement() { - return this == MOVEMENT || this == BOTH; - } - - public boolean locksRotation() { - return this == ROTATION || this == BOTH; - } - - public String getDisplayKey() { - switch (this) { - case MOVEMENT: - return "ability.lockType.movement"; - case ROTATION: - return "ability.lockType.rotation"; - case BOTH: - return "ability.lockType.both"; - default: - return "ability.lockType.both"; - } - } - - public static String[] getDisplayKeys() { - return new String[]{ - "ability.lockType.movement", - "ability.lockType.rotation", - "ability.lockType.both" - }; - } - - public static LockType fromOrdinal(int ordinal) { - LockType[] values = values(); - if (ordinal >= 0 && ordinal < values.length) { - return values[ordinal]; - } - return BOTH; - } -} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java index 479776930..5b3fc38ea 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java @@ -3,7 +3,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Condition.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/Condition.java similarity index 99% rename from src/main/java/kamkeel/npcs/controllers/data/ability/Condition.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/conditions/Condition.java index 1183fe858..2dbb0d6c0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Condition.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/Condition.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.conditions; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java index 6c8e0c24e..08c5b5da2 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionFilter.java @@ -1,7 +1,5 @@ package kamkeel.npcs.controllers.data.ability.conditions; -import kamkeel.npcs.controllers.data.ability.UserType; - public enum ConditionFilter { CASTER, TARGET, diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java index bf98c73c9..eca7aded0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHasEffect.java @@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java index d47e19506..263cd28af 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java @@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.conditions; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityIconData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/AbilityIconData.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityIconData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/AbilityIconData.java index 2120e9ca6..77dd746ea 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityIconData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/AbilityIconData.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data; +import kamkeel.npcs.controllers.data.ability.Ability; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java similarity index 96% rename from src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java index 5a80166c7..fe684da20 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ChainedAbility.java @@ -1,11 +1,15 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import noppes.npcs.api.ability.IChainedAbility; +import kamkeel.npcs.util.FileNameHelper; import java.util.ArrayList; import java.util.List; @@ -74,7 +78,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { } public ChainedAbility(String name) { - this.name = name != null ? name : ""; + setName(name); } // ═══════════════════════════════════════════════════════════════════ @@ -94,7 +98,8 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { } public void setName(String name) { - this.name = name != null ? name : ""; + String fallback = (this.name != null && !this.name.isEmpty()) ? this.name : "Chain"; + this.name = FileNameHelper.sanitizeName(name, fallback); } /** @@ -103,7 +108,9 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { * Converts & color codes to § for rendering. */ public String getDisplayName() { - String result = (displayName != null && !displayName.isEmpty()) ? displayName : name; + String result = (displayName != null && !displayName.isEmpty()) + ? displayName + : FileNameHelper.toDisplayName(name); return result != null ? result.replaceAll("&([0-9a-fk-or])", "\u00A7$1") : ""; } @@ -403,7 +410,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction { public void readNBT(NBTTagCompound nbt) { id = nbt.getString("Id"); - name = nbt.getString("Name"); + setName(nbt.getString("Name")); displayName = nbt.getString("DisplayName"); enabled = !nbt.hasKey("Enabled") || nbt.getBoolean("Enabled"); weight = nbt.hasKey("Weight") ? nbt.getInteger("Weight") : 10; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/IAbilityAction.java similarity index 84% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/IAbilityAction.java index 612cd9145..59a296f7b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/IAbilityAction.java @@ -1,5 +1,8 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityAction; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java index 94850470f..acb16a2a9 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java @@ -1,6 +1,8 @@ package kamkeel.npcs.controllers.data.ability.data; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import net.minecraft.nbt.NBTTagCompound; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityCustomEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityCustomEffect.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityCustomEffect.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityCustomEffect.java index 22428b704..4ded168d5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityCustomEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityCustomEffect.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.effect; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPotionEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityPotionEffect.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPotionEffect.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityPotionEffect.java index cb2984946..7fe6422f0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPotionEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/AbilityPotionEffect.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.effect; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IEffectAction.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/IEffectAction.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IEffectAction.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/IEffectAction.java index 2f0323c76..b674bd51f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IEffectAction.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/effect/IEffectAction.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.effect; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java index 99c51b23c..b4ceb830f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyAnchorData.java @@ -1,6 +1,6 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyAnchorData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java similarity index 99% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java index d54f205cc..130b7d2ba 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyBarrierData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyChargingData.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyChargingData.java index c12cec47f..ec53aaeb5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyChargingData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyChargingData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java similarity index 72% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java index ad3d603e5..0825fcfef 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyCombatData.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; +import kamkeel.npcs.controllers.data.ability.enums.HitType; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyCombatData; @@ -14,10 +15,25 @@ public class EnergyCombatData implements IEnergyCombatData { public boolean explosive = false; public float explosionRadius = 3.0f; public float explosionDamageFalloff = 0.5f; + public HitType hitType = HitType.SINGLE; + public int multiHitDelayTicks = 5; public EnergyCombatData() { } + public EnergyCombatData(float damage, float knockback, float knockbackUp, + boolean explosive, float explosionRadius, float explosionDamageFalloff, + HitType hitType, int multiHitDelay) { + this.damage = damage; + this.knockback = knockback; + this.knockbackUp = knockbackUp; + this.explosive = explosive; + this.explosionRadius = explosionRadius; + this.explosionDamageFalloff = explosionDamageFalloff; + this.hitType = hitType; + this.multiHitDelayTicks = multiHitDelay; + } + public EnergyCombatData(float damage, float knockback, float knockbackUp, boolean explosive, float explosionRadius, float explosionDamageFalloff) { this.damage = damage; @@ -95,6 +111,8 @@ public class EnergyCombatData implements IEnergyCombatData { nbt.setBoolean("explosive", explosive); nbt.setFloat("explosionRadius", explosionRadius); nbt.setFloat("explosionDamageFalloff", explosionDamageFalloff); + nbt.setInteger("hitType", hitType.ordinal()); + nbt.setInteger("multiHitDelayTicks", multiHitDelayTicks); } public void readNBT(NBTTagCompound nbt) { @@ -104,15 +122,21 @@ public class EnergyCombatData implements IEnergyCombatData { explosive = nbt.hasKey("explosive") && nbt.getBoolean("explosive"); explosionRadius = nbt.hasKey("explosionRadius") ? nbt.getFloat("explosionRadius") : 3.0f; explosionDamageFalloff = nbt.hasKey("explosionDamageFalloff") ? nbt.getFloat("explosionDamageFalloff") : 0.5f; + hitType = HitType.fromOrdinal(nbt.hasKey("hitType") ? nbt.getInteger("hitType") : 0); + multiHitDelayTicks = nbt.hasKey("multiHitDelayTicks") ? nbt.getInteger("multiHitDelayTicks") : 5; // Sanitize if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 7.0f; if (Float.isNaN(knockback) || Float.isInfinite(knockback) || knockback < 0) knockback = 1.0f; if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius) || explosionRadius < 0) explosionRadius = 3.0f; + if (multiHitDelayTicks < 1) multiHitDelayTicks = 1; } public EnergyCombatData copy() { - return new EnergyCombatData(damage, knockback, knockbackUp, + EnergyCombatData copy = new EnergyCombatData(damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff); + copy.hitType = hitType; + copy.multiHitDelayTicks = multiHitDelayTicks; + return copy; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyDisplayData.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyDisplayData.java index 6363a69cb..f15f31b6d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyDisplayData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyDisplayData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyHomingData.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyHomingData.java index 4f75c0841..a0825fac1 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyHomingData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyHomingData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLifespanData.java similarity index 96% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLifespanData.java index 2c8d1f6a1..fcb516242 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLifespanData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyLifespanData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLightningData.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLightningData.java index b8423e7aa..72a412cfd 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyLightningData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.api.ability.data.IEnergyLightningData; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyPanelData.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyPanelData.java index 3a5206f21..c57e0bbf5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyPanelData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyPanelData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyTrajectoryData.java similarity index 99% rename from src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyTrajectoryData.java index 10014fb5f..f888472d7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/energy/EnergyTrajectoryData.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability.data; +package kamkeel.npcs.controllers.data.ability.data.energy; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.common.util.Constants; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityEffectActionEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityEffectActionEntry.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityEffectActionEntry.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityEffectActionEntry.java index 245d153b2..6540e1f5e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityEffectActionEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityEffectActionEntry.java @@ -1,6 +1,7 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.entry; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityToggleEntry.java similarity index 79% rename from src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityToggleEntry.java index 4a758bead..d200fefa6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ToggleEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/AbilityToggleEntry.java @@ -1,4 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.entry; + +import kamkeel.npcs.controllers.data.ability.Ability; /** * Tracks the state of a single active toggle for an entity. @@ -7,12 +9,12 @@ package kamkeel.npcs.controllers.data.ability; * State is 1-based: state 1 = first ON state, state 2 = second, etc. * State 0 means OFF (entry should not exist in the map). */ -public class ToggleEntry { +public class AbilityToggleEntry { private final Ability ability; private int tickCount; private int state; - public ToggleEntry(Ability ability, int state) { + public AbilityToggleEntry(Ability ability, int state) { this.ability = ability; this.tickCount = 0; this.state = Math.max(1, state); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/ChainedAbilityEntry.java similarity index 98% rename from src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/ChainedAbilityEntry.java index a5bb7571b..85fa819ff 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/ChainedAbilityEntry.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/entry/ChainedAbilityEntry.java @@ -1,6 +1,8 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.data.entry; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import net.minecraft.nbt.NBTTagCompound; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPhase.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AbilityPhase.java similarity index 85% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPhase.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/AbilityPhase.java index af1bd8d30..a0549b09a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityPhase.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AbilityPhase.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; public enum AbilityPhase { IDLE, // Not executing diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AnchorPoint.java similarity index 97% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/AnchorPoint.java index 959aa2cf4..567713d34 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AnchorPoint.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/AnchorPoint.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines anchor points for ability charging effects. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/enums/HitType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/HitType.java new file mode 100644 index 000000000..ad1f07d0e --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/HitType.java @@ -0,0 +1,32 @@ +package kamkeel.npcs.controllers.data.ability.enums; + +/** + * Shared hit behavior for energy projectiles. + */ +public enum HitType { + SINGLE, + PIERCE, + MULTI; + + public static HitType fromOrdinal(int ordinal) { + HitType[] values = values(); + if (ordinal >= 0 && ordinal < values.length) { + return values[ordinal]; + } + return SINGLE; + } + + @Override + public String toString() { + switch (this) { + case SINGLE: + return "ability.hitType.single"; + case PIERCE: + return "ability.hitType.pierce"; + case MULTI: + return "ability.hitType.multi"; + default: + return name(); + } + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/enums/InvulnerableMode.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/InvulnerableMode.java new file mode 100644 index 000000000..fc6dc5169 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/InvulnerableMode.java @@ -0,0 +1,66 @@ +package kamkeel.npcs.controllers.data.ability.enums; + +/** + * Defines when a caster is invulnerable while an ability is executing. + */ +public enum InvulnerableMode { + NONE, + WINDUP, + ACTIVE, + BOTH; + + public boolean invulnerableDuringWindup() { + return this == WINDUP || this == BOTH; + } + + public boolean invulnerableDuringActive() { + return this == ACTIVE || this == BOTH; + } + + /** + * Treat BURST_DELAY as windup-like for interruption/damage immunity checks. + */ + public boolean isInvulnerableInPhase(AbilityPhase phase) { + if (phase == null) return false; + switch (phase) { + case WINDUP: + case BURST_DELAY: + return invulnerableDuringWindup(); + case ACTIVE: + return invulnerableDuringActive(); + default: + return false; + } + } + + public String getDisplayKey() { + switch (this) { + case WINDUP: + return "ability.invulnerable.windup"; + case ACTIVE: + return "ability.invulnerable.active"; + case BOTH: + return "ability.invulnerable.both"; + case NONE: + default: + return "ability.invulnerable.none"; + } + } + + public static String[] getDisplayKeys() { + return new String[]{ + "ability.invulnerable.none", + "ability.invulnerable.windup", + "ability.invulnerable.active", + "ability.invulnerable.both" + }; + } + + public static InvulnerableMode fromOrdinal(int ordinal) { + InvulnerableMode[] values = values(); + if (ordinal >= 0 && ordinal < values.length) { + return values[ordinal]; + } + return NONE; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/LockMovementType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/LockMode.java similarity index 87% rename from src/main/java/kamkeel/npcs/controllers/data/ability/LockMovementType.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/LockMode.java index 1b81a3bb3..47d7b1d1d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/LockMovementType.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/LockMode.java @@ -1,9 +1,9 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines when an ability locks NPC movement and rotation. */ -public enum LockMovementType { +public enum LockMode { /** * No movement lock - NPC can move freely during both phases. */ @@ -71,10 +71,10 @@ public enum LockMovementType { } /** - * Get LockMovementType from ordinal, with fallback to WINDUP. + * Get LockMode from ordinal, with fallback to WINDUP. */ - public static LockMovementType fromOrdinal(int ordinal) { - LockMovementType[] values = values(); + public static LockMode fromOrdinal(int ordinal) { + LockMode[] values = values(); if (ordinal >= 0 && ordinal < values.length) { return values[ordinal]; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/RotationMode.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/RotationMode.java similarity index 92% rename from src/main/java/kamkeel/npcs/controllers/data/ability/RotationMode.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/RotationMode.java index e1142b571..f8f1ec860 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/RotationMode.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/RotationMode.java @@ -1,8 +1,8 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines what happens to NPC rotation during ability phases. - * Used with a LockMovementType to control WHEN it applies. + * Used with a LockMode to control WHEN it applies. */ public enum RotationMode { /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetFilter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetFilter.java similarity index 92% rename from src/main/java/kamkeel/npcs/controllers/data/ability/TargetFilter.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetFilter.java index 0247cc170..91d5b2434 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetFilter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetFilter.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Filter for determining which entities an ability should affect in AOE mode. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetingMode.java similarity index 93% rename from src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetingMode.java index 7e84c6a14..b32b04c9a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/TargetingMode.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/TargetingMode.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines how an ability selects its target(s). diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/UserType.java b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/UserType.java similarity index 90% rename from src/main/java/kamkeel/npcs/controllers/data/ability/UserType.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/enums/UserType.java index d68693f59..7055174f8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/UserType.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/enums/UserType.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.enums; /** * Defines which entity types can use an ability. diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java b/src/main/java/kamkeel/npcs/controllers/data/ability/extender/IAbilityExtender.java similarity index 96% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/extender/IAbilityExtender.java index f3e7ccfd8..7f76919cb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityExtender.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/extender/IAbilityExtender.java @@ -1,5 +1,7 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.extender; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.Ability; import net.minecraft.entity.EntityLivingBase; /** diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java index 4aa9d54ae..0a05bc3d6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldBuilder.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.gui; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.AbilityCustomEffect; -import kamkeel.npcs.controllers.data.ability.AbilityEffectActionEntry; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.IEffectAction; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityCustomEffect; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityEffectActionEntry; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import noppes.npcs.client.gui.advanced.SubGuiCustomEffectSelect; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java index d34620c75..3bbd80f9b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/AbilityFieldDefs.java @@ -2,9 +2,9 @@ package kamkeel.npcs.controllers.data.ability.gui; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AbilityCustomEffect; -import kamkeel.npcs.controllers.data.ability.AbilityEffectActionEntry; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityCustomEffect; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityEffectActionEntry; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.builder.FieldType; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/GuiAbilityInterface.java similarity index 98% rename from src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/gui/GuiAbilityInterface.java index 4bf2ffd58..0ff9b31f1 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiAbilityInterface.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/GuiAbilityInterface.java @@ -1,4 +1,4 @@ -package noppes.npcs.client.gui.util; +package kamkeel.npcs.controllers.data.ability.gui; import kamkeel.npcs.client.renderer.TelegraphRenderer; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -11,6 +11,8 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import noppes.npcs.client.ClientEventHandler; +import noppes.npcs.client.gui.util.GuiNPCInterface2; +import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.entity.EntityNPCInterface; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; @@ -40,7 +42,7 @@ public class GuiAbilityInterface extends GuiNPCInterface2 { /** * NPC facing direction - affects anchor points and visual facing */ - protected static float NPC_FACING_YAW = 160.0f; + public static float NPC_FACING_YAW = 160.0f; /** * Camera pitch (slight downward angle) */ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityFieldProvider.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IAbilityFieldProvider.java similarity index 89% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityFieldProvider.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/gui/IAbilityFieldProvider.java index f196b307b..615234a7c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IAbilityFieldProvider.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IAbilityFieldProvider.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.gui; +import kamkeel.npcs.controllers.data.ability.Ability; import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IChainedAbilityFieldProvider.java similarity index 88% rename from src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/gui/IChainedAbilityFieldProvider.java index 95e8b70e0..8eec036f5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/IChainedAbilityFieldProvider.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/gui/IChainedAbilityFieldProvider.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.gui; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; diff --git a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/AbilityPreviewExecutor.java similarity index 98% rename from src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/preview/AbilityPreviewExecutor.java index bde8ae194..930a1a198 100644 --- a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/AbilityPreviewExecutor.java @@ -1,16 +1,16 @@ -package noppes.npcs.client.gui.util; +package kamkeel.npcs.controllers.data.ability.preview; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; -import kamkeel.npcs.controllers.data.ability.PreviewEntityHandler; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import kamkeel.npcs.controllers.data.ability.gui.GuiAbilityInterface; import noppes.npcs.controllers.data.Animation; import noppes.npcs.controllers.data.AnimationData; import noppes.npcs.entity.EntityCustomNpc; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/PreviewEntityHandler.java similarity index 86% rename from src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/preview/PreviewEntityHandler.java index 13c8bc526..d5917260c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/PreviewEntityHandler.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/preview/PreviewEntityHandler.java @@ -1,4 +1,4 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.preview; import net.minecraft.entity.Entity; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java index 4f7021705..c5aedfb39 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java @@ -2,10 +2,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -43,7 +43,7 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge { this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 20.0f; this.minRange = 4.0f; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.windUpTicks = 20; // LINE telegraph showing charge path diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java index 6db0f832e..c7b88961e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -71,7 +71,7 @@ public class AbilityCutter extends Ability implements IAbilityCutter { this.name = "Cutter"; this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 8.0f; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; this.cooldownTicks = 0; this.windUpTicks = 20; this.telegraphType = TelegraphType.CONE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java index b5342e3d8..26600ef0c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java @@ -2,8 +2,8 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -91,6 +91,8 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { // Type-specific runtime state private transient DashDirection chosenDirection; + private transient double preDashMotionX; + private transient double preDashMotionZ; public AbilityDash() { this.typeId = "ability.cnpc.dash"; @@ -98,7 +100,7 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 20.0f; this.minRange = 0.0f; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; this.cooldownTicks = 0; this.windUpTicks = 5; // No telegraph for dash - it's a quick evasive move @@ -125,6 +127,8 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { + preDashMotionX = caster.motionX; + preDashMotionZ = caster.motionZ; initMovement(caster, dashDistance, dashSpeed); if (dashMode == DashMode.DIRECTIONAL) { @@ -151,37 +155,46 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { if (checkTimeout(tick)) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } if (movementDirection == null) { + finishDash(caster); signalCompletion(); return; } if (checkStall(caster, tick)) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } updatePrevPosition(caster); if (getDistanceTraveledSq(caster) >= (double) dashDistance * dashDistance) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } if (checkBlocked(caster, dashSpeed)) { - stopMomentum(caster); + finishDash(caster); signalCompletion(); return; } - // Move caster (motionY left to gravity for skip arc) - applyVelocity(caster, dashSpeed); + // Move caster (motionY left to gravity for skip arc). + // Directional mode layers the dash over incoming movement so momentum + // entering/exiting the dash stays fluid. + if (dashMode == DashMode.DIRECTIONAL) { + applyHorizontalMomentum(caster, + preDashMotionX + movementDirection.xCoord * dashSpeed, + preDashMotionZ + movementDirection.zCoord * dashSpeed); + } else { + applyVelocity(caster, dashSpeed); + } if (!isPreview()) { // Trail particles @@ -191,24 +204,28 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { @Override public void onComplete(EntityLivingBase caster, EntityLivingBase target) { - stopMomentum(caster); + finishDash(caster); } @Override public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) { - stopMomentum(caster); + finishDash(caster); } @Override public void cleanup() { super.cleanup(); chosenDirection = null; + preDashMotionX = 0; + preDashMotionZ = 0; } @Override public void resetForBurst() { super.resetForBurst(); chosenDirection = null; + preDashMotionX = 0; + preDashMotionZ = 0; } @Override @@ -279,6 +296,14 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { return chosenDirection; } + private void finishDash(EntityLivingBase caster) { + if (dashMode == DashMode.DIRECTIONAL) { + applyHorizontalMomentum(caster, preDashMotionX, preDashMotionZ); + } else { + stopMomentum(caster); + } + } + @SideOnly(Side.CLIENT) @Override public void getAbilityDefinitions(List defs) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java index d4f9c474b..9a1a2c180 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEffect.java @@ -4,13 +4,13 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityCustomEffect; -import kamkeel.npcs.controllers.data.ability.AbilityEffectActionEntry; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityCustomEffect; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityEffectActionEntry; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; @@ -73,7 +73,7 @@ public class AbilityEffect extends Ability implements IAbilityEffect { this.typeId = "ability.cnpc.effect"; this.name = "Effect"; this.targetingMode = TargetingMode.SELF; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.windUpTicks = 30; this.telegraphType = TelegraphType.NONE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java index 1a1555f11..5224f5bc7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; @@ -52,7 +52,7 @@ public class AbilityGuard extends Ability implements IAbilityGuard { this.typeId = "ability.cnpc.guard"; this.name = "Guard"; this.targetingMode = TargetingMode.SELF; - this.lockMovement = LockMovementType.ACTIVE; + this.lockMovement = LockMode.ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 0; this.interruptible = false; // Hard to interrupt while guarding diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java index a9c505a56..64638510a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java @@ -2,7 +2,7 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java index 7e61f04c0..03299a67f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; @@ -38,7 +38,7 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit { this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 5.0f; this.minRange = 0.0f; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.windUpTicks = 30; this.telegraphType = TelegraphType.LINE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java index 331b91920..07810bfe3 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java @@ -226,7 +226,7 @@ public abstract class AbilityMovement extends Ability { * By sending S12 manually here — before friction — the client receives the full * velocity and moves at the correct speed. */ - private void sendPlayerVelocity(EntityLivingBase caster) { + protected void sendPlayerVelocity(EntityLivingBase caster) { if (caster instanceof EntityPlayerMP) { ((EntityPlayerMP) caster).playerNetServerHandler.sendPacket( new S12PacketEntityVelocity(caster)); @@ -234,11 +234,11 @@ public abstract class AbilityMovement extends Ability { } /** - * Apply movement velocity to the caster (horizontal only, Y unchanged). + * Apply horizontal momentum and sync to client. */ - protected void applyVelocity(EntityLivingBase caster, float speed) { - caster.motionX = movementDirection.xCoord * speed; - caster.motionZ = movementDirection.zCoord * speed; + protected void applyHorizontalMomentum(EntityLivingBase caster, double motionX, double motionZ) { + caster.motionX = motionX; + caster.motionZ = motionZ; if (!isPreview()) { if (caster instanceof EntityPlayerMP) { sendPlayerVelocity(caster); @@ -248,36 +248,27 @@ public abstract class AbilityMovement extends Ability { } } + /** + * Apply movement velocity to the caster (horizontal only, Y unchanged). + */ + protected void applyVelocity(EntityLivingBase caster, float speed) { + applyHorizontalMomentum(caster, movementDirection.xCoord * speed, movementDirection.zCoord * speed); + } + /** * Apply movement velocity with zero vertical motion. * Used for ground-locked movement like Charge. */ protected void applyVelocityFlat(EntityLivingBase caster, float speed) { - caster.motionX = movementDirection.xCoord * speed; + applyHorizontalMomentum(caster, movementDirection.xCoord * speed, movementDirection.zCoord * speed); caster.motionY = 0; - caster.motionZ = movementDirection.zCoord * speed; - if (!isPreview()) { - if (caster instanceof EntityPlayerMP) { - sendPlayerVelocity(caster); - } else { - caster.velocityChanged = true; - } - } } /** * Stop all horizontal momentum. */ protected void stopMomentum(EntityLivingBase caster) { - caster.motionX = 0; - caster.motionZ = 0; - if (!isPreview()) { - if (caster instanceof EntityPlayerMP) { - sendPlayerVelocity(caster); - } else { - caster.velocityChanged = true; - } - } + applyHorizontalMomentum(caster, 0, 0); } // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java index 1aba9ff3c..2c9945299 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java @@ -3,8 +3,8 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.Entity; @@ -42,7 +42,7 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile { this.maxRange = 20.0f; this.cooldownTicks = 0; this.windUpTicks = 15; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; // No telegraph for projectile - it's a ranged attack this.telegraphType = TelegraphType.NONE; this.showTelegraph = false; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java index d600e006d..b04dd2766 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; @@ -35,7 +35,7 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { this.name = "Shockwave"; this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 8.0f; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 25; this.telegraphType = TelegraphType.CIRCLE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index 079bcbf0f..84afb11b6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -68,7 +68,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam { this.targetingMode = TargetingMode.AOE_SELF; // Can also be AOE_TARGET to leap to target this.windUpTicks = 30; this.cooldownTicks = 0; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.minRange = 2.0f; this.maxRange = 15.0f; this.telegraphType = TelegraphType.CIRCLE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java index cc2deb4ea..1319e07ca 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java @@ -3,9 +3,9 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.block.Block; @@ -78,7 +78,7 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport { this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 10; - this.lockMovement = LockMovementType.NO; + this.lockMovement = LockMode.NO; // No telegraph for teleport - it's instant repositioning this.telegraphType = TelegraphType.NONE; this.showTelegraph = false; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java index 64e3e5b46..acd0e1378 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java @@ -2,8 +2,8 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityZone.ZoneShape; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java index 59d2a280f..c20871dd6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetFilter; -import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import net.minecraft.entity.EntityLivingBase; @@ -54,7 +54,7 @@ public class AbilityVortex extends Ability implements IAbilityVortex { this.name = "Vortex"; this.targetingMode = TargetingMode.AOE_SELF; this.maxRange = 15.0f; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 30; this.telegraphType = TelegraphType.CIRCLE; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java index a1d1af290..968ff03ba 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityZone.java @@ -3,11 +3,11 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.entity.EntityAbilityZone; @@ -65,7 +65,7 @@ public abstract class AbilityZone extends Ability { this.colorData = defaultColors; this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 20.0f; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.cooldownTicks = 0; this.allowedBy = UserType.BOTH; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java index 841750d1c..980a1dfeb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java @@ -3,8 +3,8 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.entity.EntityAbilityBarrier; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java index 0feda043a..9fd5ad973 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBeam.java @@ -3,13 +3,14 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.RotationMode; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.HitType; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.RotationMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +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.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -36,7 +37,7 @@ public class AbilityBeam extends AbilityEnergyProjectile impl public AbilityBeam() { super( new EnergyDisplayData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f), - new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f), + new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f, HitType.MULTI, 5), new EnergyHomingData(0.4f, true, 0.1f, 15.0f), new EnergyLifespanData(150.0f, 200) ); @@ -47,9 +48,9 @@ public class AbilityBeam extends AbilityEnergyProjectile impl this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 40; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.rotationMode = RotationMode.LOCKED; - this.rotationPhase = LockMovementType.ACTIVE; + this.rotationPhase = LockMode.ACTIVE; this.telegraphType = TelegraphType.CIRCLE; this.showTelegraph = true; this.windUpAnimationName = "Ability_Beam_Windup"; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java index 1a9194282..0d2d2fc99 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDisc.java @@ -3,12 +3,13 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.HitType; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +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.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -38,7 +39,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl public AbilityDisc() { super( new EnergyDisplayData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f), - new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f), + new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f, HitType.PIERCE, 5), new EnergyHomingData(0.6f, true, 0.12f, 18.0f), new EnergyLifespanData(150.0f, 200) ); @@ -49,7 +50,7 @@ public class AbilityDisc extends AbilityEnergyProjectile impl this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 60; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.CIRCLE; this.showTelegraph = true; this.windUpAnimationName = "Ability_Disc_Windup"; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java index 1663f2447..99b431477 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityDome.java @@ -3,10 +3,10 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityDome; @@ -37,7 +37,7 @@ public class AbilityDome extends AbilityBarrier { this.targetingMode = TargetingMode.SELF; this.cooldownTicks = 100; this.windUpTicks = 30; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.RING; this.showTelegraph = true; this.windUpAnimationName = ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java index 59917889e..461f3b50d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergy.java @@ -3,8 +3,8 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.builder.FieldDef; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 50b68554e..cfe9457a6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -2,18 +2,21 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.enums.HitType; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +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.EnergyTrajectoryData; import kamkeel.npcs.controllers.data.ability.data.ProjectileData; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; 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.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -54,6 +57,10 @@ public abstract class AbilityEnergyProjectile // Transient entity state protected transient E[] entities; + protected transient boolean[] projectileSpawned; + protected transient int spawnedCount; + protected transient String[] chargeVisualIds; + protected transient EntityLivingBase chargeVisualCaster; // ==================== CONSTRUCTOR ==================== @@ -76,7 +83,7 @@ public abstract class AbilityEnergyProjectile /** * Create a single entity for the given projectile index. - * Called during windup (tick 1) and burst refire (onExecute when entities are null). + * Called when a projectile actually fires. */ protected abstract E createEntity(EntityLivingBase caster, EntityLivingBase target, Vec3 spawnPos, EnergyDisplayData resolved, int index); @@ -208,32 +215,99 @@ public abstract class AbilityEnergyProjectile // ==================== ENTITY CREATION ==================== - /** - * Create all entities for this ability. - * Sets effects, source ability, and sibling links. - */ - protected E[] createAllEntities(EntityLivingBase caster, EntityLivingBase target) { - E[] newEntities = createEntityArray(projectileCount); - for (int i = 0; i < projectileCount; i++) { - Vec3 spawnPos = getSpawnPosition(caster, i); - EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); - newEntities[i] = createEntity(caster, target, spawnPos, resolved, i); - newEntities[i].setEffects(this.effects); - newEntities[i].setSourceAbility(this); + protected void initRuntimeState(EntityLivingBase caster) { + entities = createEntityArray(projectileCount); + projectileSpawned = new boolean[projectileCount]; + spawnedCount = 0; + chargeVisualIds = new String[projectileCount]; + chargeVisualCaster = caster; + } + + protected E createProjectileEntity(EntityLivingBase caster, EntityLivingBase target, int index) { + Vec3 spawnPos = getSpawnPosition(caster, index); + EnergyDisplayData resolved = projectiles[index].resolveDisplay(displayData); + E entity = createEntity(caster, target, spawnPos, resolved, index); + entity.setEffects(this.effects); + entity.setSourceAbility(this); + return entity; + } + + protected void spawnProjectileEntity(E entity, int index) { + if (entity == null) return; + spawnAbilityEntity(entity); + entities[index] = entity; + if (!projectileSpawned[index]) { + projectileSpawned[index] = true; + spawnedCount++; } - return newEntities; + } + + protected void spawnAndFireProjectile(EntityLivingBase caster, EntityLivingBase target, int index) { + if (entities != null && entities[index] != null && !entities[index].isDead) return; + E entity = createProjectileEntity(caster, target, index); + + // Initialize launch position/motion BEFORE spawn so the + // initial spawn packet already has centered look-vector data on all clients. + fireEntitySafe(entity, caster, target); + spawnProjectileEntity(entity, index); + } + + /** + * Hook for launch target selection. + * Default returns null so projectiles launch along owner look vector. + */ + protected EntityLivingBase getLaunchTarget(EntityLivingBase caster, EntityLivingBase target) { + return null; } /** * Fire an entity if it's valid. * Clears charging state first so the entity can move and check collisions. */ - protected void fireEntitySafe(E entity, EntityLivingBase target) { + protected void fireEntitySafe(E entity, EntityLivingBase caster, EntityLivingBase target) { if (entity != null && !entity.isDead) { if (entity.isCharging()) { entity.setCharging(false); } - fireEntity(entity, target); + fireEntity(entity, getLaunchTarget(caster, target)); + } + } + + protected String getChargeVisualId(EntityLivingBase caster, int index) { + return "energy_charge:" + caster.getEntityId() + ":" + executionStartTime + ":" + burstIndex + ":" + index; + } + + protected void spawnChargeVisual(EntityLivingBase caster, EntityLivingBase target, int index, int chargeDuration) { + if (isPreview() || caster == null || caster.worldObj == null || caster.worldObj.isRemote) return; + if (chargeVisualIds == null || index < 0 || index >= chargeVisualIds.length) return; + + E previewEntity = createProjectileEntity(caster, target, index); + previewEntity.setPreviewMode(true); + previewEntity.setPreviewOwner(caster); + setupEntityCharging(previewEntity, projectiles[index], index); + previewEntity.setChargeDuration(Math.max(1, chargeDuration)); + + String id = getChargeVisualId(caster, index); + chargeVisualIds[index] = id; + chargeVisualCaster = caster; + EnergyChargeSpawnPacket.sendToTracking(id, previewEntity, caster); + } + + protected void removeChargeVisual(EntityLivingBase caster, int index) { + if (isPreview() || caster == null || caster.worldObj == null || caster.worldObj.isRemote) return; + if (chargeVisualIds == null || index < 0 || index >= chargeVisualIds.length) return; + + String id = chargeVisualIds[index]; + if (id != null && !id.isEmpty()) { + EnergyChargeRemovePacket.sendToTracking(id, caster); + chargeVisualIds[index] = null; + } + } + + protected void removeAllChargeVisuals(EntityLivingBase caster) { + if (chargeVisualIds == null || caster == null) return; + for (int i = 0; i < chargeVisualIds.length; i++) { + removeChargeVisual(caster, i); } } @@ -252,6 +326,8 @@ public abstract class AbilityEnergyProjectile @Override public void detach() { entities = null; + projectileSpawned = null; + spawnedCount = 0; } @Override @@ -274,21 +350,31 @@ public abstract class AbilityEnergyProjectile if (caster.worldObj.isRemote && !isPreview()) return; if (tick == 1) { - entities = createAllEntities(caster, target); - for (int i = 0; i < projectileCount; i++) { - EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); - if (isPreview()) { - setupEntityPreview(entities[i], caster, resolved, projectiles[i], i); - } else { - setupEntityCharging(entities[i], projectiles[i], i); + initRuntimeState(caster); + + if (isPreview()) { + for (int i = 0; i < projectileCount; i++) { + E previewEntity = createProjectileEntity(caster, target, i); + EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); + setupEntityPreview(previewEntity, caster, resolved, projectiles[i], i); + spawnAbilityEntity(previewEntity); + entities[i] = previewEntity; + projectileSpawned[i] = true; + spawnedCount++; } - spawnAbilityEntity(entities[i]); + return; + } + + for (int i = 0; i < projectileCount; i++) { + spawnChargeVisual(caster, target, i, windUpTicks); } } } @Override public void resetForBurst() { + removeAllChargeVisuals(chargeVisualCaster); + // Non-overlap: kill old entities before next burst creates new ones if (!burstOverlap) { cleanup(); @@ -296,54 +382,71 @@ public abstract class AbilityEnergyProjectile // Clear reference so the next iteration creates fresh entities. // For overlap mode, old entities stay alive (tracked in burstEntities). entities = null; + projectileSpawned = null; + spawnedCount = 0; } @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { - // Create entities if they don't exist yet. - // They'll already exist if onWindUpTick ran (windup replay), otherwise we create here. - if (entities == null) { - entities = createAllEntities(caster, target); - - // If there's a fire delay, put unfired entities in charging state - // to prevent collision checks before they're actually fired. - // Entity[0] will be fired immediately below, so skip it. - if (fireDelay > 0 && projectileCount > 1) { - for (int i = 1; i < projectileCount; i++) { - if (entities[i] != null) { - entities[i].setCharging(true); - } + // GUI preview keeps the original non-world preview entity flow. + if (isPreview()) { + if (entities == null) { + initRuntimeState(caster); + for (int i = 0; i < projectileCount; i++) { + E previewEntity = createProjectileEntity(caster, target, i); + EnergyDisplayData resolved = projectiles[i].resolveDisplay(displayData); + setupEntityPreview(previewEntity, caster, resolved, projectiles[i], i); + spawnAbilityEntity(previewEntity); + entities[i] = previewEntity; + projectileSpawned[i] = true; + spawnedCount++; } } - for (E entity : entities) { - spawnAbilityEntity(entity); + fireEntitySafe(entities[0], caster, target); + if (fireDelay <= 0) { + for (int i = 1; i < projectileCount; i++) { + fireEntitySafe(entities[i], caster, target); + } } + return; } - // Fire first projectile immediately - fireEntitySafe(entities[0], target); + if (entities == null || projectileSpawned == null || entities.length != projectileCount) { + initRuntimeState(caster); + } - // Fire remaining projectiles if no delay + removeAllChargeVisuals(caster); + + // Fire first projectile immediately. + spawnAndFireProjectile(caster, target, 0); + + // Fire remaining projectiles immediately if there is no stagger delay. if (fireDelay <= 0) { for (int i = 1; i < projectileCount; i++) { - fireEntitySafe(entities[i], target); + spawnAndFireProjectile(caster, target, i); + } + } else { + // During active stagger, keep client-only charging visuals for unfired projectiles. + for (int i = 1; i < projectileCount; i++) { + spawnChargeVisual(caster, target, i, fireDelay * i); } } } @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - if (entities == null) { + if (entities == null || projectileSpawned == null) { signalCompletion(); return; } - // Fire staggered projectiles + // Fire staggered projectiles: spawn real world entity only when it is time to fire. if (fireDelay > 0) { for (int i = 1; i < projectileCount; i++) { if (tick == fireDelay * i) { - fireEntitySafe(entities[i], target); + removeChargeVisual(caster, i); + spawnAndFireProjectile(caster, target, i); } } } @@ -357,21 +460,21 @@ public abstract class AbilityEnergyProjectile } } - // Check if all entities are dead or unreachable - boolean allDead = true; - for (E entity : entities) { - if (entity == null || entity.isDead) continue; + // Before all projectiles have fired, the ability is still active by definition. + boolean allDead = spawnedCount >= projectileCount; + if (allDead) { + for (E entity : entities) { + if (entity == null || entity.isDead) continue; - // Check if entity was removed from world (chunk unload, etc.) - // Skip this check for preview entities - they're not registered in the world - if (!isPreview() && tick > 5 && entity.worldObj != null - && entity.worldObj.getEntityByID(entity.getEntityId()) != entity) { - entity.setDead(); - continue; + if (!isPreview() && tick > 5 && entity.worldObj != null + && entity.worldObj.getEntityByID(entity.getEntityId()) != entity) { + entity.setDead(); + continue; + } + + allDead = false; + break; } - - allDead = false; - break; } // Failsafe timeout: force completion if active phase exceeded max possible flight time @@ -391,15 +494,19 @@ public abstract class AbilityEnergyProjectile @Override public void onComplete(EntityLivingBase caster, EntityLivingBase target) { + removeAllChargeVisuals(caster); } @Override public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) { + removeAllChargeVisuals(caster); cleanup(); } @Override public void cleanup() { + removeAllChargeVisuals(chargeVisualCaster); + if (entities != null) { for (int i = 0; i < entities.length; i++) { if (entities[i] != null && !entities[i].isDead) { @@ -409,6 +516,10 @@ public abstract class AbilityEnergyProjectile } } entities = null; + projectileSpawned = null; + spawnedCount = 0; + chargeVisualIds = null; + chargeVisualCaster = null; } // ==================== TELEGRAPH ==================== @@ -557,6 +668,22 @@ public abstract class AbilityEnergyProjectile combatData.explosionDamageFalloff = falloff; } + public int getHitType() { + return combatData.hitType.ordinal(); + } + + public void setHitType(int hitType) { + this.combatData.hitType = HitType.fromOrdinal(hitType); + } + + public int getMultiHitDelayTicks() { + return combatData.multiHitDelayTicks; + } + + public void setMultiHitDelayTicks(int delay) { + this.combatData.multiHitDelayTicks = Math.max(1, delay); + } + // Lifespan data @Override public float getMaxDistance() { @@ -696,6 +823,15 @@ public abstract class AbilityEnergyProjectile @SideOnly(Side.CLIENT) @Override public final void getAbilityDefinitions(List defs) { + defs.add(FieldDef.enumField("ability.hitType", HitType.class, + () -> HitType.fromOrdinal(getHitType()), + (v) -> setHitType(v.ordinal())) + .hover("ability.hover.hitType")); + defs.add(FieldDef.intField("ability.multiHitDelay", this::getMultiHitDelayTicks, this::setMultiHitDelayTicks) + .range(1, 200) + .visibleWhen(() -> getHitType() == HitType.MULTI.ordinal()) + .hover("ability.hover.multiHitDelay")); + // Type-specific fields first addTypeDefinitions(defs); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java index 0ffa52946..522890d47 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityLaserShot.java @@ -2,14 +2,11 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.RotationMode; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.*; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +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.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; @@ -25,8 +22,8 @@ import noppes.npcs.client.gui.builder.FieldDef; import java.util.List; /** - * Laser Shot ability: Fast expanding thin line that pierces through targets. - * Travels in a straight line from origin, damaging all entities it passes through. + * Laser Shot ability: Fast expanding thin line. + * Travels in a straight line from origin. * Single-projectile only (always projectileCount=1). */ public class AbilityLaserShot extends AbilityEnergyProjectile implements IAbilityLaserShot { @@ -34,12 +31,11 @@ public class AbilityLaserShot extends AbilityEnergyProjectile implem this.minRange = 5.0f; this.cooldownTicks = 0; this.windUpTicks = 30; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.CIRCLE; this.showTelegraph = true; this.windUpAnimationName = "Ability_Orb_Windup"; @@ -117,7 +117,7 @@ public class AbilityOrb extends AbilityEnergyProjectile implem orb.setKnockbackUp(0.0f); orb.setProjectileCount(2); orb.setFireDelay(2); - a.setLockMovement(LockMovementType.WINDUP_AND_ACTIVE); + a.setLockMovement(LockMode.WINDUP_AND_ACTIVE); a.setMaxRange(75.0f); a.setBurstEnabled(true); a.setBurstAmount(15); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java index 2d9c515a4..f7cbaa8fd 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityShield.java @@ -3,12 +3,12 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.UserType; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.enums.UserType; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityPanel; @@ -39,7 +39,7 @@ public class AbilityShield extends AbilityBarrier { this.targetingMode = TargetingMode.SELF; this.cooldownTicks = 60; this.windUpTicks = 10; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.telegraphType = TelegraphType.NONE; this.showTelegraph = false; this.windUpAnimationName = ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java index 202938655..ca9cbc7eb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySlicer.java @@ -3,12 +3,12 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyCombatData; +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.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; @@ -23,14 +23,13 @@ import java.util.List; /** * Energy Slicer ability: Fires a thin, wide blade projectile in a straight line. - * JJK Cleave/Dismantle inspired. Piercing by default. + * JJK Cleave/Dismantle inspired. * Extends AbilityEnergyProjectile to leverage the existing projectile infrastructure. */ public class AbilitySlicer extends AbilityEnergyProjectile { private float sliceWidth = 3.0f; private float sliceThickness = 0.15f; - private boolean piercing = true; public AbilitySlicer() { super( @@ -46,7 +45,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile { this.minRange = 3.0f; this.cooldownTicks = 40; this.windUpTicks = 15; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.LINE; this.showTelegraph = true; this.windUpAnimationName = ""; @@ -62,8 +61,7 @@ public class AbilitySlicer extends AbilityEnergyProjectile { caster.worldObj, caster, target, spawnPos.xCoord, spawnPos.yCoord, spawnPos.zCoord, sliceWidth, sliceThickness, - resolved, combatData, homingData, lightningData, lifespanData, trajectoryData, - piercing + resolved, combatData, homingData, lightningData, lifespanData, trajectoryData ); } @@ -122,14 +120,12 @@ public class AbilitySlicer extends AbilityEnergyProjectile { protected void writeTypeSpecificNBT(NBTTagCompound nbt) { nbt.setFloat("sliceWidth", sliceWidth); nbt.setFloat("sliceThickness", sliceThickness); - nbt.setBoolean("piercing", piercing); } @Override protected void readTypeSpecificNBT(NBTTagCompound nbt) { this.sliceWidth = nbt.hasKey("sliceWidth") ? nbt.getFloat("sliceWidth") : 3.0f; this.sliceThickness = nbt.hasKey("sliceThickness") ? nbt.getFloat("sliceThickness") : 0.15f; - this.piercing = !nbt.hasKey("piercing") || nbt.getBoolean("piercing"); } // ==================== TYPE-SPECIFIC GETTERS ==================== @@ -158,14 +154,6 @@ public class AbilitySlicer extends AbilityEnergyProjectile { this.sliceThickness = Math.max(0.05f, thickness); } - public boolean isPiercing() { - return piercing; - } - - public void setPiercing(boolean piercing) { - this.piercing = piercing; - } - // ==================== TYPE-SPECIFIC GUI ==================== @SideOnly(Side.CLIENT) @@ -186,8 +174,6 @@ public class AbilitySlicer extends AbilityEnergyProjectile { FieldDef.floatField("gui.width", this::getSliceWidth, this::setSliceWidth).range(0.5f, 100.0f), FieldDef.floatField("gui.thickness", this::getSliceThickness, this::setSliceThickness).range(0.05f, 100.0f) )); - defs.add(FieldDef.boolField("ability.piercing", this::isPiercing, this::setPiercing) - .hover("ability.hover.piercing")); defs.add(FieldDef.row( FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).range(1.0f, 500.0f), FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).range(1, 1200) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java index 2cc64bde4..942036d41 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java @@ -2,9 +2,9 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import kamkeel.npcs.controllers.data.telegraph.Telegraph; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; @@ -49,7 +49,7 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { this.targetingMode = TargetingMode.AGGRO_TARGET; this.maxRange = 15.0f; this.minRange = 0.0f; - this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.lockMovement = LockMode.WINDUP_AND_ACTIVE; this.cooldownTicks = 0; this.windUpTicks = 60; // Circle telegraph to show range diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java index 5237485b8..1d820851d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java @@ -3,11 +3,11 @@ package kamkeel.npcs.controllers.data.ability.type.energy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.LockMovementType; -import kamkeel.npcs.controllers.data.ability.TargetingMode; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.ability.enums.LockMode; +import kamkeel.npcs.controllers.data.ability.enums.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPanelData; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBarrier; import kamkeel.npcs.entity.EntityAbilityPanel; @@ -40,7 +40,7 @@ public class AbilityWall extends AbilityBarrier { this.minRange = 2.0f; this.cooldownTicks = 80; this.windUpTicks = 20; - this.lockMovement = LockMovementType.WINDUP; + this.lockMovement = LockMode.WINDUP; this.telegraphType = TelegraphType.LINE; this.showTelegraph = true; this.windUpAnimationName = ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityTargetHelper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/util/AbilityTargetHelper.java similarity index 95% rename from src/main/java/kamkeel/npcs/controllers/data/ability/AbilityTargetHelper.java rename to src/main/java/kamkeel/npcs/controllers/data/ability/util/AbilityTargetHelper.java index b45bd2b02..7f4c772c7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityTargetHelper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/util/AbilityTargetHelper.java @@ -1,5 +1,6 @@ -package kamkeel.npcs.controllers.data.ability; +package kamkeel.npcs.controllers.data.ability.util; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -84,7 +85,7 @@ public class AbilityTargetHelper { * @return true if the target should be affected */ public static boolean shouldAffect(EntityLivingBase caster, Entity target, - TargetFilter filter, boolean includeSelf) { + TargetFilter filter, boolean includeSelf) { if (target == caster) return includeSelf; if (!(target instanceof EntityLivingBase)) return false; if (!target.isEntityAlive()) return false; diff --git a/src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java b/src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java new file mode 100644 index 000000000..2349f1975 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java @@ -0,0 +1,68 @@ +package kamkeel.npcs.controllers.data.energycharge; + +import kamkeel.npcs.entity.EntityEnergyProjectile; +import net.minecraft.world.World; + +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Client-side manager for packet-driven charging preview entities. + * These entities are never spawned into the world; they are ticked and rendered manually. + */ +public class EnergyChargePreviewManager { + + public static EnergyChargePreviewManager ClientInstance; + + private final ConcurrentHashMap previews = new ConcurrentHashMap(); + + public static void initClient() { + ClientInstance = new EnergyChargePreviewManager(); + } + + public void addPreview(String instanceId, EntityEnergyProjectile entity) { + if (instanceId == null || entity == null) return; + previews.put(instanceId, entity); + } + + public void removePreview(String instanceId) { + if (instanceId == null) return; + previews.remove(instanceId); + } + + public Collection getPreviews() { + return previews.values(); + } + + public boolean hasPreviews() { + return !previews.isEmpty(); + } + + public void clear() { + previews.clear(); + } + + /** + * Tick all preview entities and remove invalid/expired entries. + */ + public void tick(World world) { + if (world == null) { + clear(); + return; + } + + Iterator iterator = previews.values().iterator(); + while (iterator.hasNext()) { + EntityEnergyProjectile entity = iterator.next(); + if (entity == null || entity.isDead || entity.worldObj != world) { + iterator.remove(); + continue; + } + entity.onUpdate(); + if (entity.isDead) { + iterator.remove(); + } + } + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java index 02fbbdc4e..f1309bb97 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBarrier.java @@ -1,7 +1,7 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayerMP; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index b80926aab..ab374bf41 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -1,25 +1,24 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +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; +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.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; import noppes.npcs.LogWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -33,6 +32,11 @@ import java.util.List; */ public class EntityAbilityBeam extends EntityEnergyProjectile { + private enum BeamMode { + ANCHORED, + FREE_TRAIL + } + // Beam shape properties private float beamWidth = 0.3f; private float headSize = 0.5f; @@ -41,22 +45,18 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { private List trailPoints = new ArrayList<>(); private static final int MAX_TRAIL_POINTS = 200; private static final double MIN_POINT_DISTANCE = 0.2; + private static final int TRAIL_COMPACT_THRESHOLD = 128; + private int trailStartIndex = 0; // Head position relative to origin private double headOffsetX, headOffsetY, headOffsetZ; private double prevHeadOffsetX, prevHeadOffsetY, prevHeadOffsetZ; - // Origin stays fixed (or follows owner) - private boolean attachedToOwner = true; - - // Whether to render tail orb (only when anchored) - private boolean renderTailOrb = true; + // Beam behavior mode (single source of truth). + private BeamMode beamMode = BeamMode.ANCHORED; // Charging state (beam-specific) private float chargeOffsetDistance = 1.0f; - - // Trail fading for non-anchored beams (comet effect) - private boolean fadeTrail = false; private int trailFadeTime = 20; // Ticks for trail to fully fade private List trailPointAges = new ArrayList<>(); @@ -67,6 +67,55 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { super(world); } + private static BeamMode modeFromAnchored(boolean anchored) { + return anchored ? BeamMode.ANCHORED : BeamMode.FREE_TRAIL; + } + + private boolean isAnchoredMode() { + return beamMode == BeamMode.ANCHORED; + } + + private boolean isFadingMode() { + return beamMode == BeamMode.FREE_TRAIL; + } + + private int getActiveTrailSize() { + return Math.max(0, trailPoints.size() - trailStartIndex); + } + + private void resetHeadOffsets() { + headOffsetX = 0; + headOffsetY = 0; + headOffsetZ = 0; + prevHeadOffsetX = 0; + prevHeadOffsetY = 0; + prevHeadOffsetZ = 0; + } + + private void resetTrailStorage() { + trailPoints.clear(); + trailPointAges.clear(); + trailStartIndex = 0; + } + + private void setBeamMode(BeamMode mode) { + beamMode = mode != null ? mode : BeamMode.ANCHORED; + if (isFadingMode()) { + while (trailPointAges.size() < trailPoints.size()) { + trailPointAges.add(0); + } + while (trailPointAges.size() > trailPoints.size()) { + trailPointAges.remove(trailPointAges.size() - 1); + } + } else { + if (trailStartIndex > 0 && trailStartIndex < trailPoints.size()) { + trailPoints = new ArrayList(trailPoints.subList(trailStartIndex, trailPoints.size())); + } + trailStartIndex = 0; + trailPointAges.clear(); + } + } + /** * Full constructor with all parameters using data classes. * @@ -91,17 +140,11 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { this.beamWidth = beamWidth; this.headSize = headSize; - // Anchored mode controls whether origin follows owner and tail orb is rendered - this.attachedToOwner = anchoredMode; - this.renderTailOrb = anchoredMode; + // Beam mode controls tail attachment/orb/fading behavior. + setBeamMode(modeFromAnchored(anchoredMode)); // Initialize head offset at origin (0,0,0 relative) - this.headOffsetX = 0; - this.headOffsetY = 0; - this.headOffsetZ = 0; - this.prevHeadOffsetX = 0; - this.prevHeadOffsetY = 0; - this.prevHeadOffsetZ = 0; + resetHeadOffsets(); // Add initial trail point at origin (relative 0,0,0) trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); @@ -115,166 +158,58 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { } public void setupCharging(EnergyAnchorData anchor, int chargeDuration, float chargeOffsetDistance) { - setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; + setupChargingState(anchor, chargeDuration); this.chargeOffsetDistance = chargeOffsetDistance; - this.anchorData = anchor; - this.fadeTrail = !attachedToOwner; - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + clearMotion(); } public void setupPreview(EntityLivingBase owner, float beamWidth, float headSize, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, float chargeOffsetDistance) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); + setupPreviewState(owner, display, lightning, anchor, chargeDuration); // Set visual properties this.beamWidth = beamWidth; this.headSize = headSize; - this.size = headSize; - this.displayData = display; - this.lightningData = lightning; + setVisualSize(headSize); - // Set charging state - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; this.chargeOffsetDistance = chargeOffsetDistance; - this.anchorData = anchor; - - // Initial position at anchor point - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData, chargeOffsetDistance); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; + setChargeOriginFromAnchor(owner, anchorData, chargeOffsetDistance); // Initialize head offsets - this.headOffsetX = 0; - this.headOffsetY = 0; - this.headOffsetZ = 0; - this.prevHeadOffsetX = 0; - this.prevHeadOffsetY = 0; - this.prevHeadOffsetZ = 0; - - // Attach to owner for anchor following - this.attachedToOwner = true; - this.renderTailOrb = true; + resetHeadOffsets(); // Clear motion - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + clearMotion(); } /** * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - setCharging(false); - - // Origin (tail) stays at the charged position - startX = posX; - startY = posY; - startZ = posZ; + startPreviewFiringDefault(); // Reset head offset to origin - headOffsetX = 0; - headOffsetY = 0; - headOffsetZ = 0; - prevHeadOffsetX = 0; - prevHeadOffsetY = 0; - prevHeadOffsetZ = 0; - - // Origin no longer follows owner after firing - attachedToOwner = false; - - // Sync prev position to prevent visual jump on first frame - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; + resetHeadOffsets(); // Reset trail - trailPoints.clear(); - trailPointAges.clear(); + resetTrailStorage(); trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); - - // Fire forward based on owner facing direction - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - motionX = -Math.sin(yaw) * getSpeed(); - motionY = 0; - motionZ = Math.cos(yaw) * getSpeed(); - } else { - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + if (isFadingMode()) trailPointAges.add(0); } /** * Start the beam firing (exit charging mode). * Called by ability when windup ends. - *

- * For anchored beams: origin follows owner, head starts at charged position - * For non-anchored beams: origin fixed at charged position, head starts there */ public void startFiring(EntityLivingBase target) { - setCharging(false); - - // For player casters, snap to look vector for crosshair-aligned launch - snapToPlayerLookVector(); - - // Origin (tail) stays at the charged position - where the orb was - // This is the same for both anchored and non-anchored modes - startX = posX; - startY = posY; - startZ = posZ; + startMovingTowardTargetFromStartDefault(target); // Head starts at the origin (tail position) - headOffsetX = 0; - headOffsetY = 0; - headOffsetZ = 0; - prevHeadOffsetX = 0; - prevHeadOffsetY = 0; - prevHeadOffsetZ = 0; - - // Origin no longer follows owner after firing starts - // (for anchored mode, tail is fixed in space; for non-anchored, there's no tail) - attachedToOwner = false; + resetHeadOffsets(); // Initialize trail with just the origin point - trailPoints.clear(); - trailPointAges.clear(); + resetTrailStorage(); trailPoints.add(Vec3.createVectorHelper(0, 0, 0)); - if (fadeTrail) trailPointAges.add(0); - - // Calculate velocity toward target (head starts at origin = startX/Y/Z) - Entity owner = getOwnerEntity(); - - if (target != null) { - double dx = target.posX - startX; - double dy = (target.posY + target.getEyeHeight()) - startY; - double dz = target.posZ - startZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); - } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } + if (isFadingMode()) trailPointAges.add(0); if (DEBUG_LOGGING && !worldObj.isRemote) { LogWriter.info("[Beam] startFiring: origin=(" + startX + "," + startY + "," + startZ + @@ -312,7 +247,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { prevHeadOffsetZ = headOffsetZ; // Age trail points for fading effect - if (fadeTrail) { + if (isFadingMode()) { ageTrailPoints(); } @@ -346,7 +281,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { headOffsetZ += motionZ; // Update origin if attached to owner - if (attachedToOwner) { + if (isAnchoredMode()) { Entity owner = getOwnerEntity(); if (owner != null) { startX = owner.posX; @@ -374,7 +309,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { String.format("%.2f", headOffsetZ) + ") origin=(" + String.format("%.2f", startX) + "," + String.format("%.2f", startY) + "," + - String.format("%.2f", startZ) + ") trail=" + trailPoints.size()); + String.format("%.2f", startZ) + ") trail=" + getActiveTrailSize()); } // Skip collision checks on first few ticks @@ -420,25 +355,43 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Age trail points and remove old ones (for comet effect). */ private void ageTrailPoints() { - // Age all trail points - for (int i = 0; i < trailPointAges.size(); i++) { + if (trailStartIndex >= trailPoints.size()) { + resetTrailStorage(); + return; + } + + // Age all active trail points. + for (int i = trailStartIndex; i < trailPointAges.size(); i++) { trailPointAges.set(i, trailPointAges.get(i) + 1); } - // Remove trail points that have fully faded - while (!trailPointAges.isEmpty() && trailPointAges.get(0) >= trailFadeTime) { - trailPointAges.remove(0); - if (!trailPoints.isEmpty()) { - trailPoints.remove(0); - } + // Move logical start index forward for fully faded points. + while (trailStartIndex < trailPointAges.size() && trailPointAges.get(trailStartIndex) >= trailFadeTime) { + trailStartIndex++; + } + + if (trailStartIndex >= trailPoints.size()) { + resetTrailStorage(); + return; + } + + // Compact stale prefix to avoid unbounded growth of dead entries. + if (trailStartIndex >= TRAIL_COMPACT_THRESHOLD && trailStartIndex * 2 >= trailPoints.size()) { + trailPoints = new ArrayList(trailPoints.subList(trailStartIndex, trailPoints.size())); + trailPointAges = new ArrayList(trailPointAges.subList(trailStartIndex, trailPointAges.size())); + trailStartIndex = 0; } } private void addTrailPoint() { + if (trailStartIndex >= trailPoints.size()) { + resetTrailStorage(); + } + // Trail points are RELATIVE to origin if (trailPoints.isEmpty()) { trailPoints.add(Vec3.createVectorHelper(headOffsetX, headOffsetY, headOffsetZ)); - if (fadeTrail) { + if (isFadingMode()) { trailPointAges.add(0); } return; @@ -453,12 +406,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { if (dist >= MIN_POINT_DISTANCE) { trailPoints.add(Vec3.createVectorHelper(headOffsetX, headOffsetY, headOffsetZ)); - if (fadeTrail) { + if (isFadingMode()) { trailPointAges.add(0); } // Limit trail length (only if not using fading - fading handles its own cleanup) - if (!fadeTrail) { + if (!isFadingMode()) { while (trailPoints.size() > MAX_TRAIL_POINTS) { trailPoints.remove(0); } @@ -476,7 +429,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { headOffsetZ += motionZ; // Update origin if attached to owner - if (attachedToOwner) { + if (isAnchoredMode()) { Entity owner = getOwnerEntity(); if (owner != null) { startX = owner.posX; @@ -546,24 +499,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { double prevHeadWorldY = startY + prevHeadOffsetY; double prevHeadWorldZ = startZ + prevHeadOffsetZ; - Vec3 currentPos = Vec3.createVectorHelper(prevHeadWorldX, prevHeadWorldY, prevHeadWorldZ); - Vec3 nextPos = Vec3.createVectorHelper(headX, headY, headZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - if (isExplosive()) { - posX = blockHit.hitVec.xCoord; - posY = blockHit.hitVec.yCoord; - posZ = blockHit.hitVec.zCoord; - doExplosion(); - } - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(prevHeadWorldX, prevHeadWorldY, prevHeadWorldZ, headX, headY, headZ), true); } private void checkEntityCollision(double headX, double headY, double headZ) { @@ -577,24 +513,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - - if (DEBUG_LOGGING) { - LogWriter.info("[Beam] DEAD: Entity collision with " + entity.getClass().getSimpleName() + " at tick " + ticksExisted); + if (processEntityHit(entity, headX, headY, headZ)) { + if (DEBUG_LOGGING) { + LogWriter.info("[Beam] DEAD: Entity collision with " + entity.getClass().getSimpleName() + " at tick " + ticksExisted); + } + return; } - hasHit = true; - - if (isExplosive()) { - posX = headX; - posY = headY; - posZ = headZ; - doExplosion(); - } else { - applyDamage(entity); - } - - this.setDead(); - return; } } @@ -617,7 +541,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { } public void setAttachedToOwner(boolean attached) { - this.attachedToOwner = attached; + setBeamMode(modeFromAnchored(attached)); } /** @@ -640,7 +564,13 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Get trail points. These are RELATIVE to origin (startX/Y/Z). */ public List getTrailPoints() { - return trailPoints; + if (trailStartIndex <= 0) { + return trailPoints; + } + if (trailStartIndex >= trailPoints.size()) { + return Collections.emptyList(); + } + return trailPoints.subList(trailStartIndex, trailPoints.size()); } public double getOriginX() { @@ -659,28 +589,34 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { * Whether the tail orb should be rendered (only true in anchored mode). */ public boolean shouldRenderTailOrb() { - return renderTailOrb; + return isAnchoredMode(); } /** * Whether the beam is attached to its owner (anchored mode). */ public boolean isAttachedToOwner() { - return attachedToOwner; + return isAnchoredMode(); } /** * Whether trail should fade (comet effect for non-anchored beams). */ public boolean hasFadingTrail() { - return fadeTrail; + return isFadingMode(); } /** * Get trail point ages for fading calculation. */ public List getTrailPointAges() { - return trailPointAges; + if (trailStartIndex <= 0) { + return trailPointAges; + } + if (trailStartIndex >= trailPointAges.size()) { + return Collections.emptyList(); + } + return trailPointAges.subList(trailStartIndex, trailPointAges.size()); } /** @@ -702,17 +638,26 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { this.prevHeadOffsetX = this.headOffsetX; this.prevHeadOffsetY = this.headOffsetY; this.prevHeadOffsetZ = this.headOffsetZ; - this.attachedToOwner = !nbt.hasKey("AttachedToOwner") || nbt.getBoolean("AttachedToOwner"); - this.renderTailOrb = !nbt.hasKey("RenderTailOrb") || nbt.getBoolean("RenderTailOrb"); readChargingNBT(nbt); this.chargeOffsetDistance = nbt.hasKey("ChargeOffsetDistance") ? nbt.getFloat("ChargeOffsetDistance") : 1.0f; - this.fadeTrail = nbt.hasKey("FadeTrail") && nbt.getBoolean("FadeTrail"); this.trailFadeTime = nbt.hasKey("TrailFadeTime") ? nbt.getInteger("TrailFadeTime") : 20; + boolean attachedLegacy = !nbt.hasKey("AttachedToOwner") || nbt.getBoolean("AttachedToOwner"); + BeamMode loadedMode = modeFromAnchored(attachedLegacy); + if (nbt.hasKey("BeamMode")) { + try { + loadedMode = BeamMode.valueOf(nbt.getString("BeamMode")); + } catch (IllegalArgumentException ignored) { + loadedMode = modeFromAnchored(attachedLegacy); + } + } else if (nbt.hasKey("FadeTrail")) { + loadedMode = nbt.getBoolean("FadeTrail") ? BeamMode.FREE_TRAIL : modeFromAnchored(attachedLegacy); + } // Read trail points (relative to origin) trailPoints.clear(); trailPointAges.clear(); + trailStartIndex = 0; if (nbt.hasKey("Trail")) { NBTTagList trailList = nbt.getTagList("Trail", 10); for (int i = 0; i < trailList.tagCount(); i++) { @@ -732,10 +677,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { trailPointAges.add(age); } } - // If trail points exist but ages don't (legacy data), initialize ages to 0 - while (trailPointAges.size() < trailPoints.size()) { - trailPointAges.add(0); - } + setBeamMode(loadedMode); } @Override @@ -745,16 +687,18 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { nbt.setDouble("HeadOffsetX", headOffsetX); nbt.setDouble("HeadOffsetY", headOffsetY); nbt.setDouble("HeadOffsetZ", headOffsetZ); - nbt.setBoolean("AttachedToOwner", attachedToOwner); - nbt.setBoolean("RenderTailOrb", renderTailOrb); + nbt.setBoolean("AttachedToOwner", isAnchoredMode()); + nbt.setBoolean("RenderTailOrb", isAnchoredMode()); + nbt.setString("BeamMode", beamMode.name()); writeChargingNBT(nbt); nbt.setFloat("ChargeOffsetDistance", chargeOffsetDistance); - nbt.setBoolean("FadeTrail", fadeTrail); + nbt.setBoolean("FadeTrail", isFadingMode()); nbt.setInteger("TrailFadeTime", trailFadeTime); // Write trail points NBTTagList trailList = new NBTTagList(); - for (Vec3 point : trailPoints) { + for (int i = trailStartIndex; i < trailPoints.size(); i++) { + Vec3 point = trailPoints.get(i); NBTTagCompound pointNbt = new NBTTagCompound(); pointNbt.setDouble("X", point.xCoord); pointNbt.setDouble("Y", point.yCoord); @@ -764,9 +708,11 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { nbt.setTag("Trail", trailList); // Write trail point ages for fading trail sync - int[] ages = new int[trailPointAges.size()]; - for (int i = 0; i < trailPointAges.size(); i++) { - ages[i] = trailPointAges.get(i); + int activeSize = isFadingMode() ? Math.max(0, trailPoints.size() - trailStartIndex) : 0; + int[] ages = new int[activeSize]; + for (int i = 0; i < activeSize; i++) { + int idx = trailStartIndex + i; + ages[i] = idx < trailPointAges.size() ? trailPointAges.get(idx) : 0; } nbt.setIntArray("TrailAges", ages); } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index d16fc7709..e8b8acc58 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -1,23 +1,19 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +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; +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.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.util.AnchorPointHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; - -import java.util.List; /** * Disc projectile - flat spinning disc with optional boomerang behavior. @@ -98,18 +94,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { * Can be fired when transitioning to active phase. */ public void setupPreview(EntityLivingBase owner, float discRadius, float discThickness, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, boolean vertical) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); - - // Set visual properties - this.displayData = display; - this.lightningData = lightning; - - // Set charging state - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupPreviewState(owner, display, lightning, anchor, chargeDuration); this.vertical = vertical; // Store target size and start at 0 for grow effect @@ -117,55 +102,16 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { this.targetDiscThickness = discThickness; this.discRadius = 0.01f; this.discThickness = 0.01f; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - - // Initial position at anchor point - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; - - // Clear motion - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); } /** * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - setCharging(false); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Sync prev position to prevent visual jump on first frame - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; - - // Fire forward based on owner facing direction - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = 0; // Fire horizontally - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } else { - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + startPreviewFiringDefault(); } /** @@ -173,36 +119,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startMoving(EntityLivingBase target) { - setCharging(false); - - // For player casters, snap to look vector for crosshair-aligned launch - snapToPlayerLookVector(); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Calculate velocity toward target - Entity owner = getOwnerEntity(); - - if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight()) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); - } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } + startMovingTowardTargetDefault(target); } @Override @@ -248,15 +165,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { } // Check wall collision - if (this.isCollidedHorizontally || this.isCollidedVertically) { - if (!worldObj.isRemote) { - hasHit = true; - if (isExplosive()) { - doExplosion(); - } - } - this.setDead(); - } + handleSolidCollisionTermination(); } @Override @@ -352,24 +261,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { } private void checkBlockCollision() { - Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ); - Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - if (isExplosive()) { - posX = blockHit.hitVec.xCoord; - posY = blockHit.hitVec.yCoord; - posZ = blockHit.hitVec.zCoord; - doExplosion(); - } - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(posX, posY, posZ, posX + motionX, posY + motionY, posZ + motionZ), true); } private void checkEntityCollision() { @@ -381,23 +273,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { posX + halfWidth, posY + halfHeight, posZ + halfWidth ); - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - - for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - - hasHit = true; - - if (isExplosive()) { - doExplosion(); - } else { - applyDamage(entity); - } - - this.setDead(); - return; - } + processEntitiesInHitBox(hitBox, posX, posY, posZ); } /** diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java index 149475d0d..8c0fc8fae 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java @@ -2,9 +2,9 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index f3a899181..56048d973 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -1,14 +1,13 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; -import kamkeel.npcs.util.AnchorPointHelper; +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; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; @@ -17,12 +16,10 @@ import net.minecraft.world.World; import noppes.npcs.EventHooks; import noppes.npcs.entity.EntityNPCInterface; -import java.util.HashSet; import java.util.List; -import java.util.Set; /** - * Laser projectile - fast expanding thin line that pierces through multiple targets. + * Laser projectile - fast expanding thin line. * No homing, travels in a straight line from origin to max distance. *

* Design inspired by LouisXIV's energy attack system. @@ -45,12 +42,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { // Lock vertical direction after firing (only update yaw, keep pitch fixed) private boolean lockVerticalDirection = false; - // Whether to die on first entity impact (hit-scan mode) - private boolean dieOnImpact = false; - - // Track hit entities to avoid double-damage - private Set hitEntities = new HashSet<>(); - // End point for rendering private double endX, endY, endZ; @@ -164,13 +155,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { } /** - * Update laser origin and direction from the owner's current anchor position and rotation. - * Called each tick so the laser follows the NPC when tracking a target. + * Update laser origin and direction from the owner's current look rotation. + * Called each tick so the laser origin stays centered on look vector. */ private void updateLaserOriginAndDirection() { Entity owner = getOwnerEntity(); if (owner == null || !(owner instanceof EntityLivingBase)) return; - EntityLivingBase livingOwner = (EntityLivingBase) owner; // Update direction from owner's current rotation @@ -188,71 +178,51 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { dirZ = Math.cos(yaw) * Math.cos(pitch); } - // Update origin: for player casters, snap to look vector so laser aligns with crosshair. - // For NPCs, use the configured anchor point. - if (owner instanceof EntityPlayer) { - double eyeY = owner.posY + owner.getEyeHeight(); - float frontDist = Math.max(0.5f, laserWidth * 0.5f); - startX = owner.posX + dirX * frontDist; - startY = eyeY + dirY * frontDist; - startZ = owner.posZ + dirZ * frontDist; - } else if (anchorData != null) { - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData); - startX = pos.xCoord; - startY = pos.yCoord; - startZ = pos.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); + if (dirLen > 0.0001) { + direction = Vec3.createVectorHelper( + direction.xCoord / dirLen, + direction.yCoord / dirLen, + direction.zCoord / dirLen + ); } else { - startX = owner.posX; - startY = owner.posY + owner.height * 0.7; - startZ = owner.posZ; - } - - // Keep entity positioned at origin - prevPosX = startX; - prevPosY = startY; - prevPosZ = startZ; - setPosition(startX, startY, startZ); - } - - public void startMoving(EntityLivingBase target) { - setCharging(false); - - // Set initial direction from owner's rotation - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - this.dirX = -Math.sin(yaw) * Math.cos(pitch); - this.dirY = -Math.sin(pitch); - this.dirZ = Math.cos(yaw) * Math.cos(pitch); - } else if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight() - 0.4) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - this.dirX = dx / len; - this.dirY = dy / len; - this.dirZ = dz / len; + Vec3 look = livingOwner.getLookVec(); + if (look != null) { + direction = look; } } - // For player casters, snap origin to look vector so laser aligns with crosshair. - // For NPCs, use current anchor position. - if (owner instanceof EntityPlayer) { - double eyeY = owner.posY + owner.getEyeHeight(); - float frontDist = Math.max(0.5f, laserWidth * 0.5f); - startX = owner.posX + dirX * frontDist; - startY = eyeY + dirY * frontDist; - startZ = owner.posZ + dirZ * frontDist; - setPosition(startX, startY, startZ); - prevPosX = startX; - prevPosY = startY; - prevPosZ = startZ; - } else { - startX = posX; - startY = posY; - startZ = posZ; + dirX = direction.xCoord; + dirY = direction.yCoord; + dirZ = direction.zCoord; + + // Keep origin centered on look vector while active and clear owner bbox. + setLookVectorLaunchPosition(livingOwner, direction, false); + syncStartPositionToCurrent(); + syncPositionStateToCurrent(false); + } + + public void startMoving(EntityLivingBase target) { + beginLookVectorLaunch(false); + + currentLength = 0.0f; + fullyExtended = false; + ticksSinceFullExtension = 0; + + // Set initial direction: target-based when available, otherwise owner look vector. + 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; + } } // Initialize end point at start (will expand from here) @@ -261,11 +231,22 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.endZ = startZ; } + private boolean setDirectionTowardTarget(EntityLivingBase target, double sourceX, double sourceY, double sourceZ) { + if (target == null) return false; + double dx = target.posX - sourceX; + double dy = (target.posY + target.getEyeHeight() - 0.4) - sourceY; + double dz = target.posZ - sourceZ; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len <= 0.0001) return false; + + dirX = dx / len; + dirY = dy / len; + dirZ = dz / len; + return true; + } + private void checkBlockCollision() { - Vec3 start = Vec3.createVectorHelper(startX, startY, startZ); - Vec3 end = Vec3.createVectorHelper(endX, endY, endZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(start, end, false, true, false); + MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, endX, endY, endZ); if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { if (!worldObj.isRemote) { @@ -315,15 +296,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { for (EntityLivingBase entity : entities) { if (shouldIgnoreEntity(entity)) continue; - if (hitEntities.contains(entity.getEntityId())) continue; + if (!canHitEntityNow(entity)) continue; // Check if entity's bounding box intersects with the laser line if (isEntityOnLine(entity)) { - hitEntities.add(entity.getEntityId()); - applyDamage(entity); - - if (dieOnImpact) { - // Stop at impact point and begin linger/fade + if (processEntityHit(entity, entity.posX, entity.posY + entity.height * 0.5, entity.posZ)) { + // Stop at impact point and terminate double dx = entity.posX - startX; double dy = (entity.posY + entity.height * 0.5) - startY; double dz = entity.posZ - startZ; @@ -332,11 +310,10 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { endX = startX + dirX * currentLength; endY = startY + dirY * currentLength; endZ = startZ + dirZ * currentLength; - fullyExtended = true; - return; // Stop checking further entities + return; } - // Piercing - don't stop, continue to next entity + // PIERCE/MULTI continue through remaining entities. } } } @@ -357,8 +334,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { double dirLenXZ = Math.sqrt(dirX * dirX + dirZ * dirZ); double dot; if (dirLenXZ > 0.001) { - dot = (vx * dirX + vz * dirZ) / (dirLenXZ * dirLenXZ) * dirLenXZ; - // Remap to full 3D parameter dot = (vx * dirX + vz * dirZ + (entity.posY + entity.height * 0.5 - startY) * dirY); } else { // Laser fires nearly straight up/down - use full 3D projection @@ -517,14 +492,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.lingerTicks = ticks; } - public boolean isDieOnImpact() { - return dieOnImpact; - } - - public void setDieOnImpact(boolean dieOnImpact) { - this.dieOnImpact = dieOnImpact; - } - public void setDirection(double x, double y, double z) { this.dirX = x; this.dirY = y; @@ -584,6 +551,24 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { return Math.max(0.0f, 1.0f - ((float) ticksSinceFullExtension / lingerTicks)); } + @Override + protected float getLaunchClearanceRadius() { + return Math.max(0.1f, laserWidth * 0.5f); + } + + @Override + public void setupCharging(EnergyAnchorData anchor, int chargeDuration) { + // Ensure charging visuals grow from a small orb based on laser width. + this.size = laserWidth; + this.currentLength = 0.0f; + this.fullyExtended = false; + this.ticksSinceFullExtension = 0; + super.setupCharging(anchor, chargeDuration); + this.endX = posX; + this.endY = posY; + this.endZ = posZ; + } + // ==================== NBT ==================== @Override @@ -593,7 +578,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { 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.dieOnImpact = nbt.getBoolean("DieOnImpact"); this.lockVerticalDirection = nbt.getBoolean("LockVerticalDir"); this.ticksSinceFullExtension = nbt.getInteger("TicksSinceExtended"); this.dirX = nbt.getDouble("DirX"); @@ -613,7 +597,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { nbt.setFloat("LaserWidth", laserWidth); nbt.setFloat("ExpansionSpeed", expansionSpeed); nbt.setInteger("LingerTicks", lingerTicks); - nbt.setBoolean("DieOnImpact", dieOnImpact); nbt.setBoolean("LockVerticalDir", lockVerticalDirection); nbt.setInteger("TicksSinceExtended", ticksSinceFullExtension); nbt.setDouble("DirX", dirX); @@ -630,42 +613,47 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { /** * Setup this laser in preview mode for GUI display. - * Laser doesn't have charging state - spawns at active phase and fires immediately. + * Uses charging-orb visuals during windup before firing. */ public void setupPreview(EntityLivingBase owner, float laserWidth, EnergyDisplayData display, - EnergyLightningData lightning, float expansionSpeed, float maxDistance) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); + EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration, + float expansionSpeed, float maxDistance) { + setupPreviewState(owner, display, lightning, anchor, chargeDuration); - // Set visual properties + // Laser-specific visual/behavior state. this.laserWidth = laserWidth; - this.size = laserWidth; - this.displayData = display; this.expansionSpeed = expansionSpeed; this.lifespanData.maxDistance = Math.min(maxDistance, 5.0f); // Limit for GUI preview - this.lightningData = lightning; + this.currentLength = 0.0f; + this.fullyExtended = false; + this.ticksSinceFullExtension = 0; - // Position at chest height - double x = owner.posX; - double y = owner.posY + owner.height * 0.7; - double z = owner.posZ; - this.setPosition(x, y, z); - this.prevPosX = x; - this.prevPosY = y; - this.prevPosZ = z; - this.startX = x; - this.startY = y; - this.startZ = z; + // Charge as an orb from tiny size to laser width while anchored. + this.targetSize = laserWidth; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); + this.endX = posX; + this.endY = posY; + this.endZ = posZ; - // Fire in owner's facing direction - float yaw = (float) Math.toRadians(owner.rotationYaw); - this.dirX = -Math.sin(yaw); - this.dirY = 0; - this.dirZ = Math.cos(yaw); - - // Initialize end point (same as start, will expand) - this.endX = x; - this.endY = y; - this.endZ = z; + // Precompute initial direction from owner look for launch. + Vec3 look = owner == null ? null : owner.getLookVec(); + if (look == null) { + if (owner != null) { + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + look = Vec3.createVectorHelper( + -Math.sin(yaw) * Math.cos(pitch), + -Math.sin(pitch), + Math.cos(yaw) * Math.cos(pitch) + ); + } else { + look = Vec3.createVectorHelper(1.0, 0.0, 0.0); + } + } + this.dirX = look.xCoord; + this.dirY = look.yCoord; + this.dirZ = look.zCoord; } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index 82447edd2..407012efd 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -1,23 +1,16 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; -import kamkeel.npcs.util.AnchorPointHelper; -import net.minecraft.entity.Entity; +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; +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.controllers.data.ability.data.energy.EnergyTrajectoryData; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; -import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; - -import java.util.List; /** * Orb projectile - spherical homing energy ball. @@ -26,6 +19,8 @@ import java.util.List; * Design inspired by LouisXIV's energy attack system. */ public class EntityAbilityOrb extends EntityEnergyProjectile { + private static final int HOMING_STARTUP_TICKS = 4; + private static final int HOMING_RAMP_TICKS = 8; public EntityAbilityOrb(World world) { super(world); @@ -56,71 +51,20 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { * Can be fired when transitioning to active phase. */ public void setupPreview(EntityLivingBase owner, float orbSize, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); - - // Set visual properties - this.displayData = display; - this.lightningData = lightning; - - // Set charging state - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupPreviewState(owner, display, lightning, anchor, chargeDuration); // Store target size and start at 0 for grow effect this.targetSize = orbSize; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - - // Initial position at anchor point - Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; - - // Clear motion - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); } /** * Start preview firing (simulates firing toward a point in front of NPC). */ public void startPreviewFiring() { - setCharging(false); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Sync prev position to prevent visual jump on first frame - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; - - // Fire forward based on owner facing direction - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(0); // Fire horizontally - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } else { - // Default: fire forward (positive X in model space) - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + startPreviewFiringDefault(); } /** @@ -128,36 +72,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { * Called by ability when windup ends. */ public void startMoving(EntityLivingBase target) { - setCharging(false); - - // For player casters, snap to look vector for crosshair-aligned launch - snapToPlayerLookVector(); - - // Update start position to current position - startX = posX; - startY = posY; - startZ = posZ; - - // Calculate velocity toward target - Entity owner = getOwnerEntity(); - - if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight()) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); - } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } + startMovingAlongLookVectorDefault(); } @Override @@ -185,15 +100,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { } // Check wall collision - if (this.isCollidedHorizontally || this.isCollidedVertically) { - if (!worldObj.isRemote) { - hasHit = true; - if (isExplosive()) { - doExplosion(); - } - } - this.setDead(); - } + handleSolidCollisionTermination(); } /** @@ -210,10 +117,26 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { if (!isTrajectoryConcluded()) { updateTrajectory(); } else { - updateHoming(); + if (ticksExisted > HOMING_STARTUP_TICKS) { + int homingTicks = ticksExisted - HOMING_STARTUP_TICKS; + if (homingTicks >= HOMING_RAMP_TICKS) { + updateHoming(); + } else { + float ramp = homingTicks / (float) HOMING_RAMP_TICKS; + updateHomingWithRamp(ramp); + } + } } } + private void updateHomingWithRamp(float ramp) { + if (ramp <= 0.0f) return; + float originalStrength = homingData.homingStrength; + homingData.homingStrength = originalStrength * ramp; + updateHoming(); + homingData.homingStrength = originalStrength; + } + private void updateTrajectory() { if (isTrajectoryConcluded()) return; // i really fucking hate math @@ -233,50 +156,21 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { } private void checkBlockCollision() { - Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ); - Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ); - // Use full raytrace that doesn't stop at liquids and checks all blocks - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - if (isExplosive()) { - posX = blockHit.hitVec.xCoord; - posY = blockHit.hitVec.yCoord; - posZ = blockHit.hitVec.zCoord; - doExplosion(); - } - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(posX, posY, posZ, posX + motionX, posY + motionY, posZ + motionZ), true); } private void checkEntityCollision() { - double hitSize = size * 0.5; + // Swept collision to avoid fast orbs skipping targets between ticks. + double nextX = posX + motionX; + double nextY = posY + motionY; + double nextZ = posZ + motionZ; + // Keep collision radius proportional to rendered orb size. + double hitSize = Math.max(0.05, size * 0.5); AxisAlignedBB hitBox = AxisAlignedBB.getBoundingBox( - posX - hitSize, posY - hitSize, posZ - hitSize, - posX + hitSize, posY + hitSize, posZ + hitSize + Math.min(posX, nextX) - hitSize, Math.min(posY, nextY) - hitSize, Math.min(posZ, nextZ) - hitSize, + Math.max(posX, nextX) + hitSize, Math.max(posY, nextY) + hitSize, Math.max(posZ, nextZ) + hitSize ); - - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - - for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - - hasHit = true; - - if (isExplosive()) { - doExplosion(); - } else { - applyDamage(entity); - } - - this.setDead(); - return; - } + processEntitiesInHitBox(hitBox, nextX, nextY, nextZ); } // ==================== GETTERS ==================== diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java index f1a89012e..653d336a2 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java @@ -2,10 +2,10 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyBarrierData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyPanelData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyPanelData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -168,6 +168,17 @@ public class EntityAbilityPanel extends EntityAbilityBarrier { this.prevPosZ = owner.prevPosZ + (Math.cos(prevYawRad) * frontDist); } + @Override + public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) { + // HELD panels are fully owner-driven each tick via updateHeld(). + // Applying network position lerp on top of owner-following causes visible jitter/stutter + // when the caster is moving, so ignore remote corrections in this mode. + if (worldObj != null && worldObj.isRemote && mode == PanelMode.HELD && getOwnerEntity() != null) { + return; + } + super.setPositionAndRotation2(x, y, z, yaw, pitch, posRotationIncrements); + } + private void updateLaunched() { // Move forward on both sides for smooth client rendering this.posX += motionX; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index f08ffeb2a..7efeec93b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -2,7 +2,7 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -10,7 +10,6 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.world.World; -import noppes.npcs.LogWriter; import noppes.npcs.NpcDamageSource; import noppes.npcs.controllers.PartyController; import noppes.npcs.controllers.data.Party; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index 129b87495..a3c034fcb 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -6,9 +6,9 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.AbilityTargetHelper; -import kamkeel.npcs.controllers.data.ability.TargetFilter; +import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; +import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper; +import kamkeel.npcs.controllers.data.ability.enums.TargetFilter; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java index 585eadc7d..01b39f80d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -5,8 +5,8 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import kamkeel.npcs.util.ByteBufUtils; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -83,6 +83,10 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit } } + public void setChargeDuration(int duration) { + this.chargeDuration = duration; + } + public float getChargeProgress() { if (chargeDuration <= 0) return 1.0f; return Math.min(1.0f, (float) chargeTick / chargeDuration); @@ -333,6 +337,24 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit readEnergyBaseNBT(nbt); } + /** + * Export spawn NBT for non-world preview rendering sync. + */ + public final NBTTagCompound exportSpawnNBT() { + NBTTagCompound nbt = new NBTTagCompound(); + writeSpawnNBT(nbt); + return nbt; + } + + /** + * Import spawn NBT for non-world preview rendering sync. + */ + public final void importSpawnNBT(NBTTagCompound nbt) { + if (nbt != null) { + readSpawnNBT(nbt); + } + } + @Override public void writeSpawnData(ByteBuf buffer) { try { diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java new file mode 100644 index 000000000..d90295912 --- /dev/null +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyExplosion.java @@ -0,0 +1,138 @@ +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.controllers.data.ability.data.energy.EnergyLightningData; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; + +/** + * Short-lived visual explosion entity for energy projectile impacts. + * This is render-only and never applies damage itself. + */ +public class EntityEnergyExplosion extends EntityEnergyAbility { + + private float maxRadius = 2.0f; + private int durationTicks = 10; + private float prevRenderRadius = 0.0f; + private float renderRadius = 0.0f; + private long renderSeed = 0L; + + public EntityEnergyExplosion(World world) { + super(world); + this.noClip = true; + this.setSize(0.1f, 0.1f); + } + + public EntityEnergyExplosion(World world, EntityEnergyProjectile source, float radius) { + this(world); + + if (source != null) { + this.ownerEntityId = source.getOwnerEntityId(); + EnergyDisplayData sourceDisplay = source.getDisplayData(); + this.displayData = sourceDisplay != null ? sourceDisplay.copy() : new EnergyDisplayData(); + EnergyLightningData sourceLightning = source.getLightningData(); + this.lightningData = sourceLightning != null ? sourceLightning.copy() : new EnergyLightningData(); + this.setPosition(source.posX, source.posY, source.posZ); + } + + setExplosionRadius(radius); + this.renderSeed = world != null ? world.rand.nextLong() : 0L; + } + + @Override + protected void entityInit() { + super.entityInit(); + } + + @Override + public void onUpdate() { + this.prevRenderRadius = this.renderRadius; + + super.onUpdate(); + + float progress = getLifeProgress(0.0f); + float eased = 1.0f - (1.0f - progress) * (1.0f - progress); + this.renderRadius = maxRadius * eased; + + if (ticksExisted >= durationTicks) { + setDead(); + } + } + + public void setExplosionRadius(float radius) { + this.maxRadius = Math.max(0.5f, sanitize(radius, 2.0f, MAX_ENTITY_RADIUS)); + this.durationTicks = Math.max(6, Math.min(18, (int) (6 + this.maxRadius * 1.8f))); + this.setSize(this.maxRadius * 2.0f, this.maxRadius * 2.0f); + } + + public float getInterpolatedRadius(float partialTicks) { + return this.prevRenderRadius + (this.renderRadius - this.prevRenderRadius) * partialTicks; + } + + public float getLifeProgress(float partialTicks) { + if (durationTicks <= 0) return 1.0f; + float age = (ticksExisted + partialTicks) / durationTicks; + return MathHelper.clamp_float(age, 0.0f, 1.0f); + } + + public long getRenderSeed() { + return renderSeed; + } + + @Override + @SideOnly(Side.CLIENT) + public boolean isInRangeToRenderDist(double distance) { + double d = Math.max(16.0D, maxRadius * 64.0D); + return distance < d * d; + } + + @Override + public boolean canBeCollidedWith() { + return false; + } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt) { + // Intentionally empty - transient visual entity. + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt) { + // Intentionally empty - transient visual entity. + } + + @Override + protected void writeSpawnNBT(NBTTagCompound nbt) { + writeEnergyBaseNBT(nbt); + nbt.setDouble("PosX", this.posX); + nbt.setDouble("PosY", this.posY); + nbt.setDouble("PosZ", this.posZ); + nbt.setFloat("ExplosionRadius", maxRadius); + nbt.setInteger("ExplosionDuration", durationTicks); + nbt.setLong("ExplosionSeed", renderSeed); + } + + @Override + protected void readSpawnNBT(NBTTagCompound nbt) { + readEnergyBaseNBT(nbt); + double x = nbt.hasKey("PosX") ? nbt.getDouble("PosX") : this.posX; + double y = nbt.hasKey("PosY") ? nbt.getDouble("PosY") : this.posY; + double z = nbt.hasKey("PosZ") ? nbt.getDouble("PosZ") : this.posZ; + this.setPosition(x, y, z); + this.prevPosX = x; + this.prevPosY = y; + this.prevPosZ = z; + this.lastTickPosX = x; + this.lastTickPosY = y; + this.lastTickPosZ = z; + setExplosionRadius(nbt.hasKey("ExplosionRadius") ? nbt.getFloat("ExplosionRadius") : 2.0f); + this.durationTicks = nbt.hasKey("ExplosionDuration") ? nbt.getInteger("ExplosionDuration") : durationTicks; + if (durationTicks <= 0) durationTicks = 10; + this.renderSeed = nbt.hasKey("ExplosionSeed") ? nbt.getLong("ExplosionSeed") : 0L; + this.renderRadius = 0.0f; + this.prevRenderRadius = 0.0f; + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 4a33e2489..278fb8001 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -3,28 +3,39 @@ 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.AbilityPotionEffect; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +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; +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; +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.controllers.data.ability.data.energy.EnergyTrajectoryData; +import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket; import kamkeel.npcs.util.AnchorPointHelper; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.EnumCreatureType; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; +import net.minecraft.util.MathHelper; +import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; +import net.minecraft.world.Explosion; import net.minecraft.world.World; +import net.minecraft.world.WorldServer; import noppes.npcs.EventHooks; import noppes.npcs.NpcDamageSource; import noppes.npcs.api.entity.IEntity; +import noppes.npcs.config.ConfigEnergy; import noppes.npcs.controllers.PartyController; import noppes.npcs.controllers.data.Party; import noppes.npcs.controllers.data.PlayerData; @@ -35,9 +46,12 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.UUID; /** * Base class for all ability projectiles (Orb, Disc, Beam, Laser, Slicer). @@ -47,7 +61,6 @@ import java.util.Map; * Design inspired by LouisXIV's energy attack system. */ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { - // ==================== ACTIVE PROJECTILE TRACKING ==================== private static final Map>> activeProjectiles = new HashMap<>(); @@ -112,6 +125,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== STATE ==================== protected boolean hasHit = false; + protected final Set hitOnceEntities = new HashSet(); + protected final Map lastHitTickByEntity = new HashMap(); // ==================== CHARGING STATE (projectile-specific) ==================== protected float targetSize = 1.0f; @@ -364,24 +379,42 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { @Override public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) { - // If position changed significantly (e.g. snap from anchor to look vector), - // skip interpolation and jump directly to prevent visual offset from crosshair + int steps = Math.max(1, posRotationIncrements); double dx = x - this.posX; double dy = y - this.posY; double dz = z - this.posZ; - if (dx * dx + dy * dy + dz * dz > 1.0) { - this.setPosition(x, y, z); - this.prevPosX = x; - this.prevPosY = y; - this.prevPosZ = z; - this.interpSteps = 0; - return; + double dotToCurrentMotion = dx * this.motionX + dy * this.motionY + dz * this.motionZ; + + // Spawn-time and large correction snaps: avoid visible startup rubber-banding. + if (worldObj != null && worldObj.isRemote) { + double distSq = dx * dx + dy * dy + dz * dz; + // Launch-time stale tracker packets can arrive one update late and point behind + // an already-predicted projectile. Skipping these avoids visible backward "bounce". + if (ticksExisted <= 24 && distSq <= 4.0D && dotToCurrentMotion < -0.0025D) { + return; + } + if (ticksExisted <= 2 || distSq > 16.0D) { + this.setPosition(x, y, z); + syncPositionState(x, y, z, true); + return; + } } this.interpTargetX = x; this.interpTargetY = y; this.interpTargetZ = z; - this.interpSteps = posRotationIncrements; + if (worldObj != null && worldObj.isRemote && ticksExisted <= 24 && dotToCurrentMotion < 0.0D) { + // Keep existing velocity when correction points opposite current travel + // to prevent launch-time reverse lerp. + this.interpTargetMotionX = this.motionX; + this.interpTargetMotionY = this.motionY; + this.interpTargetMotionZ = this.motionZ; + } else { + this.interpTargetMotionX = dx / steps; + this.interpTargetMotionY = dy / steps; + this.interpTargetMotionZ = dz / steps; + } + this.interpSteps = steps; } @Override @@ -426,6 +459,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected void applyDamage(EntityLivingBase target, float dmg, float kb) { if (previewMode) return; // Skip damage in preview mode + if (target == null || shouldIgnoreEntity(target)) return; // Fire entity impact event (may cancel or modify damage) if (!worldObj.isRemote) { @@ -438,6 +472,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Check for ability extenders (e.g., DBC Addon damage routing) boolean handled = false; + boolean defaultDamageApplied = false; if (sourceAbility != null && owner instanceof EntityLivingBase) { double dx = target.posX - posX; double dz = target.posZ - posZ; @@ -450,16 +485,19 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (!handled) { // Default damage path if (owner instanceof EntityNPCInterface) { - target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); + defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); } else if (owner instanceof EntityPlayer) { - target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); + defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); } else if (owner instanceof EntityLivingBase) { - target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); + defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); } else { - target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); + defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); } } + boolean allowSecondaryEffects = handled || dmg <= 0 || defaultDamageApplied; + if (!allowSecondaryEffects) return; + if (kb > 0) { double dx = target.posX - posX; double dz = target.posZ - posZ; @@ -493,6 +531,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { if (previewMode) return; // Skip explosion in preview mode float explosionRad = getExplosionRadius(); if (Float.isNaN(explosionRad) || explosionRad <= 0) return; + spawnExplosionRenderEntity(explosionRad); + spawnExplosionVisuals(explosionRad); worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f); Entity owner = getOwnerEntity(); @@ -507,6 +547,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { for (EntityLivingBase target : targets) { if (target == owner) continue; + if (shouldIgnoreExplosionTarget(target)) continue; double dist = Math.sqrt( Math.pow(target.posX - posX, 2) + @@ -519,53 +560,601 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { applyDamage(target, getDamage() * falloff, getKnockback() * falloff); } } + + tryDestroyTerrain(explosionRad); + } + + /** + * Explosion safety filter. + * Reuses standard projectile-friendly checks and blocks passive non-player entities. + */ + protected boolean shouldIgnoreExplosionTarget(EntityLivingBase target) { + if (target == null) return true; + if (shouldIgnoreEntity(target)) return true; + return isPassiveNonPlayerEntity(target); + } + + /** + * Passive mobs (animals/ambient/water creatures) are never damaged by energy explosions. + * Players and hostile mobs remain valid unless other friendly-fire checks reject them. + */ + protected boolean isPassiveNonPlayerEntity(EntityLivingBase target) { + if (target instanceof EntityPlayer) return false; + if (target instanceof EntityNPCInterface) return false; + if (target.isCreatureType(EnumCreatureType.monster, false)) return false; + return target.isCreatureType(EnumCreatureType.creature, false) + || target.isCreatureType(EnumCreatureType.ambient, false) + || target.isCreatureType(EnumCreatureType.waterCreature, false); + } + + /** + * Spawn a packet-driven client preview so explosions are visible as geometry, + * not only as particles. + */ + protected void spawnExplosionRenderEntity(float explosionRad) { + if (worldObj == null || worldObj.isRemote) return; + float renderRad = Math.max(0.75f, Math.min(explosionRad, 12.0f)); + EntityEnergyExplosion fx = new EntityEnergyExplosion(worldObj, this, renderRad); + EnergyExplosionSpawnPacket.sendToTracking(getExplosionVisualInstanceId(), fx, this); + } + + protected String getExplosionVisualInstanceId() { + return "energy_explosion_" + UUID.randomUUID(); + } + + /** + * Shared voxel-style explosion VFX for all energy projectile abilities. + */ + protected void spawnExplosionVisuals(float explosionRad) { + if (worldObj == null) return; + + float visualRad = Math.max(0.75f, Math.min(explosionRad, 12.0f)); + int coreBursts = Math.max(1, Math.min(4, Math.round(visualRad * 0.35f))); + int shellCount = Math.max(1, Math.min(3, Math.round(visualRad / 3.5f) + 1)); + + // Core flash. + emitRandomBurstParticle("largeexplode", coreBursts, visualRad * 0.10, visualRad * 0.08, visualRad * 0.10, 0.01); + + // Expanding voxel shells. + for (int i = 1; i <= shellCount; i++) { + double frac = i / (double) shellCount; + double shellRadius = visualRad * (0.35D + 0.65D * frac); + spawnVoxelShell(shellRadius, i, visualRad); + } + + // Residual smoke volume. + int smokeCount = Math.max(6, Math.min(40, Math.round(visualRad * 4.5f))); + emitRandomBurstParticle("smoke", smokeCount, visualRad * 0.45, visualRad * 0.30, visualRad * 0.45, 0.01); + + // Pull block fragments for stronger voxel feel. + spawnVoxelDebris(visualRad); + } + + /** + * Emit random burst particles centered around this explosion. + */ + protected void emitRandomBurstParticle(String particle, int count, + double spreadX, double spreadY, double spreadZ, + double speed) { + if (worldObj == null || count <= 0 || particle == null || particle.isEmpty()) return; + + if (worldObj.isRemote) { + for (int i = 0; i < count; i++) { + double px = posX + (rand.nextDouble() - 0.5D) * 2.0D * spreadX; + double py = posY + (rand.nextDouble() - 0.5D) * 2.0D * spreadY; + double pz = posZ + (rand.nextDouble() - 0.5D) * 2.0D * spreadZ; + double mx = rand.nextGaussian() * speed; + double my = rand.nextGaussian() * speed; + double mz = rand.nextGaussian() * speed; + worldObj.spawnParticle(particle, px, py, pz, mx, my, mz); + } + return; + } + + if (worldObj instanceof WorldServer) { + ((WorldServer) worldObj).func_147487_a(particle, posX, posY, posZ, count, spreadX, spreadY, spreadZ, speed); + } + } + + /** + * Emits a single directed voxel particle. + * Uses count=0 packet mode on server so motion values are preserved. + */ + protected void emitVoxelParticle(String particle, double x, double y, double z, + double motionX, double motionY, double motionZ) { + if (worldObj == null || particle == null || particle.isEmpty()) return; + + if (worldObj.isRemote) { + worldObj.spawnParticle(particle, x, y, z, motionX, motionY, motionZ); + return; + } + + if (worldObj instanceof WorldServer) { + ((WorldServer) worldObj).func_147487_a(particle, x, y, z, 0, motionX, motionY, motionZ, 1.0D); + } + } + + /** + * Spawn one cubic shell of explosion particles to get a blocky/voxel silhouette. + */ + protected void spawnVoxelShell(double shellRadius, int shellIndex, float visualRad) { + int steps = Math.max(2, Math.min(4, (int) Math.ceil(shellRadius * 0.55D))); + double cell = shellRadius / steps; + double jitter = Math.min(0.15D, cell * 0.20D); + double shellSpeed = 0.03D + (shellRadius / Math.max(1.0D, visualRad)) * 0.05D; + + for (int ix = -steps; ix <= steps; ix++) { + for (int iy = -steps; iy <= steps; iy++) { + for (int iz = -steps; iz <= steps; iz++) { + int edge = Math.max(Math.abs(ix), Math.max(Math.abs(iy), Math.abs(iz))); + if (edge != steps) continue; + if (((ix + iy + iz + shellIndex) & 1) != 0) continue; + + double px = posX + ix * cell + (rand.nextDouble() - 0.5D) * jitter; + double py = posY + iy * cell + (rand.nextDouble() - 0.5D) * jitter; + double pz = posZ + iz * cell + (rand.nextDouble() - 0.5D) * jitter; + + double nx = ix; + double ny = iy; + double nz = iz; + double len = Math.sqrt(nx * nx + ny * ny + nz * nz); + if (len < 0.001D) { + ny = 1.0D; + len = 1.0D; + } + nx /= len; + ny /= len; + nz /= len; + + emitVoxelParticle("explode", px, py, pz, nx * shellSpeed, ny * shellSpeed, nz * shellSpeed); + + if (((ix + iy + iz + shellIndex) % 3) == 0) { + emitVoxelParticle("smoke", px, py, pz, nx * shellSpeed * 0.35D, ny * shellSpeed * 0.35D, nz * shellSpeed * 0.35D); + } + } + } + } + } + + /** + * Spawn debris particles using nearby block textures. + */ + protected void spawnVoxelDebris(float visualRad) { + if (worldObj == null) return; + + int count = Math.max(4, Math.min(24, Math.round(visualRad * 2.5f))); + for (int i = 0; i < count; i++) { + double ox = (rand.nextDouble() - 0.5D) * visualRad * 1.2D; + double oy = (rand.nextDouble() - 0.35D) * visualRad * 0.9D; + double oz = (rand.nextDouble() - 0.5D) * visualRad * 1.2D; + + int bx = MathHelper.floor_double(posX + ox); + int by = MathHelper.floor_double(posY + oy); + int bz = MathHelper.floor_double(posZ + oz); + + Block block = worldObj.getBlock(bx, by, bz); + if (block == null || block == Blocks.air) continue; + + int blockId = Block.getIdFromBlock(block); + if (blockId <= 0) continue; + int meta = worldObj.getBlockMetadata(bx, by, bz); + + String particle = "blockcrack_" + blockId + "_" + Math.max(0, meta); + emitVoxelParticle( + particle, + posX + ox * 0.55D, + posY + oy * 0.55D, + posZ + oz * 0.55D, + ox * 0.03D, oy * 0.03D, oz * 0.03D + ); + } + } + + /** + * Optional terrain damage for energy explosions. + * Controlled server-side by ConfigEnergy.EnableEnergyExplosionBlockDamage. + */ + protected void tryDestroyTerrain(float explosionRad) { + if (worldObj == null || worldObj.isRemote) return; + if (!ConfigEnergy.EnableEnergyExplosionBlockDamage) return; + if (!worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing")) return; + + float terrainRad = Math.max(0.5f, Math.min(explosionRad, 12.0f)); + int minX = MathHelper.floor_double(posX - terrainRad); + int maxX = MathHelper.floor_double(posX + terrainRad); + int minY = MathHelper.floor_double(posY - terrainRad); + int maxY = MathHelper.floor_double(posY + terrainRad); + int minZ = MathHelper.floor_double(posZ - terrainRad); + int maxZ = MathHelper.floor_double(posZ + terrainRad); + float resistanceCutoff = Math.max(4.0f, terrainRad * 6.0f); + + Explosion context = new Explosion(worldObj, this, posX, posY, posZ, terrainRad); + context.isFlaming = false; + context.isSmoking = true; + + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + double cx = x + 0.5D - posX; + double cy = y + 0.5D - posY; + double cz = z + 0.5D - posZ; + + // Chebyshev metric yields a voxel/cubic blast shape. + double chebyshev = Math.max(Math.abs(cx), Math.max(Math.abs(cy), Math.abs(cz))); + if (chebyshev > terrainRad) continue; + + // Soften cube corners a bit for a less artificial crater edge. + double normalized = chebyshev / terrainRad; + if (normalized > 0.72D && rand.nextDouble() < (normalized - 0.72D) * 1.4D) continue; + + Block block = worldObj.getBlock(x, y, z); + if (block == null || block == Blocks.air || block == Blocks.bedrock) continue; + Material material = block.getMaterial(); + if (material == null || material == Material.air) continue; + + float resistance = block.getExplosionResistance(this, worldObj, x, y, z, posX, posY, posZ); + if (resistance > resistanceCutoff) continue; + + try { + block.onBlockExploded(worldObj, x, y, z, context); + } catch (Exception ignored) { + } + } + } + } } // ==================== LAUNCH HELPERS ==================== /** - * For player casters, snap the projectile to a position along the player's look vector. - * This ensures projectiles launch aligned with the crosshair rather than from the hand anchor. - * Only affects player casters; NPC projectiles launch from their anchor position as configured. + * Snaps the projectile position to the owner's look vector on launch. + * This ensures projectiles launch aligned with the owner's facing direction + * rather than from the last anchor point position. * Should be called in startMoving/startFiring BEFORE setting startX/Y/Z. */ - protected void snapToPlayerLookVector() { + protected void snapToLookVector() { + setLookVectorLaunchPosition(true); + } + + /** + * Shared launch transition: exit charging, snap to look-vector launch origin, + * sync start position, and optionally reset interpolation state. + * + * @param resetInterpolationState true to reset prev/lastTick/interp state to launch origin. + * @return true when look-vector launch origin was resolved from owner/look data. + */ + protected boolean beginLookVectorLaunch(boolean resetInterpolationState) { + setCharging(false); + boolean positioned = setLookVectorLaunchPosition(true); + syncStartPositionToCurrent(); + if (resetInterpolationState) { + syncPositionStateToCurrent(true); + } + return positioned; + } + + /** + * Shared launch transition with explicit owner/look vector data. + */ + protected boolean beginLookVectorLaunch(EntityLivingBase owner, Vec3 look, boolean resetInterpolationState) { + setCharging(false); + boolean positioned = setLookVectorLaunchPosition(owner, look, true); + syncStartPositionToCurrent(); + if (resetInterpolationState) { + syncPositionStateToCurrent(true); + } + return positioned; + } + + /** + * Default preview launch: snap to look-vector origin and fire along look. + */ + protected void startPreviewFiringDefault() { + beginLookVectorLaunch(true); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + } + + /** + * Default active launch: snap to look-vector origin and fire toward target when available. + */ + protected void startMovingTowardTargetDefault(EntityLivingBase target) { + beginLookVectorLaunch(false); + setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0); + } + + /** + * Default active launch from fixed origin fields (used by beam-like projectiles). + */ + protected void startMovingTowardTargetFromStartDefault(EntityLivingBase target) { + beginLookVectorLaunch(false); + setMotionTowardTargetOrLookVector(target, startX, startY, startZ, getSpeed(), getSpeed(), 0, 0); + } + + /** + * Default active launch strictly along look vector. + */ + protected void startMovingAlongLookVectorDefault() { + beginLookVectorLaunch(false); + setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0); + } + + /** + * Resolve the owner's look vector. + * Falls back to yaw/pitch math if look vector is unavailable. + */ + protected Vec3 getOwnerLookVector() { Entity owner = getOwnerEntity(); - if (!(owner instanceof EntityPlayer)) return; + if (!(owner instanceof EntityLivingBase)) return null; Vec3 look = owner.getLookVec(); - double eyeY = owner.posY + owner.getEyeHeight(); - float frontDist = Math.max(0.5f, size * 0.5f); + if (look != null) return look; - double newX = owner.posX + look.xCoord * frontDist; - double newY = eyeY + look.yCoord * frontDist; - double newZ = owner.posZ + look.zCoord * frontDist; + float yaw = (float) Math.toRadians(owner.rotationYaw); + float pitch = (float) Math.toRadians(owner.rotationPitch); + return Vec3.createVectorHelper( + -Math.sin(yaw) * Math.cos(pitch), + -Math.sin(pitch), + Math.cos(yaw) * Math.cos(pitch) + ); + } + + /** + * Shared launch origin: owner eye position + look-vector offset. + * + * @param syncPositionState When true, sync prev/lastTick/interp targets to this position. + */ + protected boolean setLookVectorLaunchPosition(boolean syncPositionState) { + Entity owner = getOwnerEntity(); + if (!(owner instanceof EntityLivingBase)) return false; + Vec3 look = getOwnerLookVector(); + if (look == null) return false; + return setLookVectorLaunchPosition((EntityLivingBase) owner, look, syncPositionState); + } + + /** + * Shared launch origin using provided owner/look data. + */ + protected boolean setLookVectorLaunchPosition(EntityLivingBase owner, Vec3 look, boolean syncPositionState) { + if (owner == null || look == null) return false; + + double originX = owner.posX; + double originY = owner.posY + owner.getEyeHeight(); + double originZ = owner.posZ; + + float frontDist = computeLaunchFrontDistance(owner, look, originX, originY, originZ); + double newX = originX + look.xCoord * frontDist; + double newY = originY + look.yCoord * frontDist; + double newZ = originZ + look.zCoord * frontDist; setPosition(newX, newY, newZ); - prevPosX = newX; - prevPosY = newY; - prevPosZ = newZ; + if (syncPositionState) { + syncPositionState(newX, newY, newZ, true); + } + return true; + } + + /** + * Radius used to clear the caster hitbox on launch. + * Subclasses may override if their effective launch footprint differs from {@code size}. + */ + protected float getLaunchClearanceRadius() { + return Math.max(0.1f, size * 0.5f); + } + + private float computeLaunchFrontDistance(EntityLivingBase owner, Vec3 look, double originX, double originY, double originZ) { + // Legacy baseline offset retained as lower bound. + float baseline = size * 0.4f; + float clearance = getLaunchClearanceRadius() + 0.05f; + + AxisAlignedBB bb = owner.boundingBox; + if (bb == null) { + return Math.max(baseline, owner.width * 0.5f + clearance); + } + + double dx = look.xCoord; + double dy = look.yCoord; + double dz = look.zCoord; + + double tx = computeRayExitT(originX, dx, bb.minX, bb.maxX); + double ty = computeRayExitT(originY, dy, bb.minY, bb.maxY); + double tz = computeRayExitT(originZ, dz, bb.minZ, bb.maxZ); + + double tExit = Math.min(tx, Math.min(ty, tz)); + if (Double.isInfinite(tExit) || tExit < 0) { + return Math.max(baseline, owner.width * 0.5f + clearance); + } + + return (float) Math.max(baseline, tExit + clearance); + } + + private double computeRayExitT(double origin, double dir, double min, double max) { + final double EPS = 1.0e-6; + if (dir > EPS) { + return (max - origin) / dir; + } else if (dir < -EPS) { + return (min - origin) / dir; + } + return Double.POSITIVE_INFINITY; + } + + /** + * Set projectile motion along owner look vector. + * + * @return true if owner/look were available. + */ + protected boolean setMotionAlongLookVector(float speed) { + Vec3 look = getOwnerLookVector(); + if (look == null) return false; + + motionX = look.xCoord * speed; + motionY = look.yCoord * speed; + motionZ = look.zCoord * speed; + return true; + } + + /** + * Set motion along owner look vector or apply explicit fallback values. + */ + protected void setMotionAlongLookVectorOrFallback(float speed, double fallbackX, double fallbackY, double fallbackZ) { + if (!setMotionAlongLookVector(speed)) { + motionX = fallbackX; + motionY = fallbackY; + motionZ = fallbackZ; + } + } + + /** + * Set projectile motion toward a target from a given source point. + * + * @return true if target was valid and motion was updated. + */ + protected boolean setMotionTowardTarget(EntityLivingBase target, double sourceX, double sourceY, double sourceZ, float speed) { + if (target == null) return false; + + double dx = target.posX - sourceX; + double dy = (target.posY + target.getEyeHeight()) - sourceY; + double dz = target.posZ - sourceZ; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len <= 0.0001) return false; + + motionX = (dx / len) * speed; + motionY = (dy / len) * speed; + motionZ = (dz / len) * speed; + return true; + } + + /** + * Set motion toward target from source point; when target is unavailable, + * fall back to owner look vector, then explicit fallback values. + */ + protected void setMotionTowardTargetOrLookVector(EntityLivingBase target, double sourceX, double sourceY, double sourceZ, float speed, + double fallbackX, double fallbackY, double fallbackZ) { + if (!setMotionTowardTarget(target, sourceX, sourceY, sourceZ, speed)) { + setMotionAlongLookVectorOrFallback(speed, fallbackX, fallbackY, fallbackZ); + } + } + + /** + * Sync start position (distance origin) to the current entity position. + */ + protected void syncStartPositionToCurrent() { + startX = posX; + startY = posY; + startZ = posZ; + } + + /** + * Sync prev/lastTick and optional interpolation state to an explicit position. + */ + protected void syncPositionState(double x, double y, double z, boolean resetInterpolation) { + prevPosX = x; + prevPosY = y; + prevPosZ = z; + lastTickPosX = x; + lastTickPosY = y; + lastTickPosZ = z; + + if (resetInterpolation) { + interpTargetX = x; + interpTargetY = y; + interpTargetZ = z; + interpTargetMotionX = motionX; + interpTargetMotionY = motionY; + interpTargetMotionZ = motionZ; + interpSteps = 0; + } + } + + /** + * Sync prev/lastTick and optional interpolation state to current entity position. + */ + protected void syncPositionStateToCurrent(boolean resetInterpolation) { + syncPositionState(posX, posY, posZ, resetInterpolation); } // ==================== CHARGING METHODS (projectile-specific) ==================== + /** + * Shared charging-state bootstrap used by both world and preview setup paths. + */ + protected void setupChargingState(EnergyAnchorData anchor, int chargeDuration) { + setCharging(true); + this.chargeDuration = chargeDuration; + this.chargeTick = 0; + this.anchorData = anchor; + } + + /** + * Shared preview bootstrap used by projectile preview setup methods. + */ + protected void setupPreviewState(EntityLivingBase owner, EnergyDisplayData display, + EnergyLightningData lightning, EnergyAnchorData anchor, + int chargeDuration) { + this.setPreviewMode(true); + this.setPreviewOwner(owner); + this.displayData = display; + this.lightningData = lightning; + setupChargingState(anchor, chargeDuration); + } + + /** + * Set size and render-size state to a single value. + */ + protected void setVisualSize(float value) { + this.size = value; + this.renderCurrentSize = value; + this.prevRenderSize = value; + } + + /** + * Set entity/start state to a fixed position used as charging/launch origin. + */ + protected void setChargeOrigin(Vec3 pos) { + if (pos == null) return; + this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); + this.prevPosX = pos.xCoord; + this.prevPosY = pos.yCoord; + this.prevPosZ = pos.zCoord; + this.startX = pos.xCoord; + this.startY = pos.yCoord; + this.startZ = pos.zCoord; + } + + /** + * Resolve and apply charging/launch origin from anchor position. + */ + protected void setChargeOriginFromAnchor(EntityLivingBase owner, EnergyAnchorData anchor) { + if (owner == null || anchor == null) return; + setChargeOrigin(AnchorPointHelper.calculateAnchorPosition(owner, anchor)); + } + + /** + * Resolve and apply charging/launch origin from anchor position with forward offset. + */ + protected void setChargeOriginFromAnchor(EntityLivingBase owner, EnergyAnchorData anchor, float offsetDistance) { + if (owner == null || anchor == null) return; + setChargeOrigin(AnchorPointHelper.calculateAnchorPosition(owner, anchor, offsetDistance)); + } + + /** + * Zero current motion. + */ + protected void clearMotion() { + this.motionX = 0; + this.motionY = 0; + this.motionZ = 0; + } + /** * Setup this entity in charging mode (for windup phase). * Grows from 0 to current size over chargeDuration ticks. * Subclasses with different charging behavior should override or use their own method. */ public void setupCharging(EnergyAnchorData anchor, int chargeDuration) { - setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupChargingState(anchor, chargeDuration); this.targetSize = this.size; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + clearMotion(); } /** @@ -861,6 +1450,142 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { combatData.setExplosionDamageFalloff(falloff); } + public int getHitType() { + return combatData.hitType.ordinal(); + } + + public void setHitType(int hitType) { + combatData.hitType = HitType.fromOrdinal(hitType); + } + + public int getMultiHitDelayTicks() { + return combatData.multiHitDelayTicks; + } + + public void setMultiHitDelayTicks(int delayTicks) { + combatData.multiHitDelayTicks = Math.max(1, delayTicks); + } + + protected boolean canHitEntityNow(EntityLivingBase entity) { + if (entity == null) return false; + + int entityId = entity.getEntityId(); + switch (combatData.hitType) { + case PIERCE: + return !hitOnceEntities.contains(entityId); + case MULTI: + Integer lastHitTick = lastHitTickByEntity.get(entityId); + if (lastHitTick == null) return true; + int delay = Math.max(1, combatData.multiHitDelayTicks); + return ticksExisted - lastHitTick >= delay; + case SINGLE: + default: + return !hasHit; + } + } + + protected void recordEntityHit(EntityLivingBase entity) { + if (entity == null) return; + int entityId = entity.getEntityId(); + hitOnceEntities.add(entityId); + lastHitTickByEntity.put(entityId, ticksExisted); + } + + protected boolean shouldTerminateAfterHit() { + return combatData.hitType == HitType.SINGLE; + } + + /** + * Shared block raytrace helper used by most projectile types. + */ + protected MovingObjectPosition rayTraceBlocks(double fromX, double fromY, double fromZ, double toX, double toY, double toZ) { + Vec3 currentPos = Vec3.createVectorHelper(fromX, fromY, fromZ); + Vec3 nextPos = Vec3.createVectorHelper(toX, toY, toZ); + return worldObj.func_147447_a(currentPos, nextPos, false, true, false); + } + + /** + * Shared block-hit handling: fires block impact hook, optional explosion, and kills projectile. + */ + protected boolean handleBlockImpact(MovingObjectPosition blockHit, boolean explodeAtHitPoint) { + if (blockHit == null || blockHit.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) return false; + + if (!worldObj.isRemote) { + EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); + } + hasHit = true; + + if (isExplosive()) { + if (explodeAtHitPoint && blockHit.hitVec != null) { + posX = blockHit.hitVec.xCoord; + posY = blockHit.hitVec.yCoord; + posZ = blockHit.hitVec.zCoord; + } + doExplosion(); + } + + this.setDead(); + return true; + } + + /** + * Apply projectile hit behavior to a target and return true if the projectile terminated. + */ + protected boolean processEntityHit(EntityLivingBase entity, double impactX, double impactY, double impactZ) { + if (entity == null || shouldIgnoreEntity(entity) || !canHitEntityNow(entity)) return false; + + recordEntityHit(entity); + if (isExplosive()) { + double oldX = posX; + double oldY = posY; + double oldZ = posZ; + posX = impactX; + posY = impactY; + posZ = impactZ; + doExplosion(); + posX = oldX; + posY = oldY; + posZ = oldZ; + } else { + applyDamage(entity); + } + + if (shouldTerminateAfterHit()) { + hasHit = true; + this.setDead(); + return true; + } + + return false; + } + + /** + * Scan entities in a hitbox and process the first/each valid hit based on hit mode. + */ + protected boolean processEntitiesInHitBox(AxisAlignedBB hitBox, double impactX, double impactY, double impactZ) { + @SuppressWarnings("unchecked") + List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); + for (EntityLivingBase entity : entities) { + if (processEntityHit(entity, impactX, impactY, impactZ)) { + return true; + } + } + return false; + } + + protected boolean handleSolidCollisionTermination() { + if (!this.isCollidedHorizontally && !this.isCollidedVertically) return false; + + if (!worldObj.isRemote) { + hasHit = true; + if (isExplosive()) { + doExplosion(); + } + } + this.setDead(); + return true; + } + // ==================== HOMING GETTERS & SETTERS ==================== public boolean isHoming() { @@ -990,6 +1715,23 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { this.motionY = nbt.getDouble("MotionY"); this.motionZ = nbt.getDouble("MotionZ"); + // Authoritative launch origin comes from spawn NBT (set during fire/startMoving). + // Sync full client position state to this origin to avoid first-tick spawn/interp pops. + this.setPosition(this.startX, this.startY, this.startZ); + this.prevPosX = this.startX; + this.prevPosY = this.startY; + this.prevPosZ = this.startZ; + this.lastTickPosX = this.startX; + this.lastTickPosY = this.startY; + this.lastTickPosZ = this.startZ; + this.interpTargetX = this.startX; + this.interpTargetY = this.startY; + this.interpTargetZ = this.startZ; + this.interpTargetMotionX = this.motionX; + this.interpTargetMotionY = this.motionY; + this.interpTargetMotionZ = this.motionZ; + this.interpSteps = 0; + // Always initialize render size from NBT size so entities // loading in from outside render distance render correctly this.renderCurrentSize = this.size; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index 29d0ac39a..bc429f939 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -1,35 +1,28 @@ package kamkeel.npcs.entity; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +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; +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.controllers.data.ability.data.energy.EnergyTrajectoryData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; -import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.World; -import noppes.npcs.EventHooks; - -import java.util.ArrayList; -import java.util.List; /** * Energy Slicer entity - a thin, wide blade projectile that flies in a straight line. - * JJK Cleave/Dismantle inspired. Piercing by default (passes through entities). + * JJK Cleave/Dismantle inspired. * Extends EntityEnergyAbility to leverage the existing projectile infrastructure. */ public class EntityEnergySlicer extends EntityEnergyProjectile { private float sliceWidth = 3.0f; private float sliceThickness = 0.15f; - private boolean piercing = true; - private List hitEntities = new ArrayList<>(); public EntityEnergySlicer(World world) { super(world); @@ -43,15 +36,13 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { float sliceWidth, float sliceThickness, EnergyDisplayData display, EnergyCombatData combat, EnergyHomingData homing, EnergyLightningData lightning, - EnergyLifespanData lifespan, EnergyTrajectoryData trajectory, - boolean piercing) { + EnergyLifespanData lifespan, EnergyTrajectoryData trajectory) { super(world); initProjectile(owner, target, x, y, z, sliceWidth, display, combat, lightning, lifespan, trajectory); this.homingData = homing; this.sliceWidth = sliceWidth; this.sliceThickness = sliceThickness; - this.piercing = piercing; calculateInitialVelocity(owner, target, x, y, z); } @@ -77,89 +68,29 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { public void setupPreview(EntityLivingBase owner, float width, float thickness, EnergyDisplayData display, EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration) { - this.setPreviewMode(true); - this.setPreviewOwner(owner); - this.displayData = display; - this.lightningData = lightning; - this.setCharging(true); - this.chargeDuration = chargeDuration; - this.chargeTick = 0; - this.anchorData = anchor; + setupPreviewState(owner, display, lightning, anchor, chargeDuration); this.targetSliceWidth = width; this.targetSliceThickness = thickness; this.sliceWidth = 0.01f; this.sliceThickness = 0.01f; - this.size = 0.01f; - this.renderCurrentSize = 0.01f; - this.prevRenderSize = 0.01f; - - Vec3 pos = kamkeel.npcs.util.AnchorPointHelper.calculateAnchorPosition(owner, anchorData); - this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; - this.startX = pos.xCoord; - this.startY = pos.yCoord; - this.startZ = pos.zCoord; - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; + setVisualSize(0.01f); + setChargeOriginFromAnchor(owner, anchorData); + clearMotion(); } /** * Start preview firing. */ public void startPreviewFiring() { - setCharging(false); - startX = posX; - startY = posY; - startZ = posZ; - prevPosX = posX; - prevPosY = posY; - prevPosZ = posZ; - - Entity owner = getOwnerEntity(); - if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - motionX = -Math.sin(yaw) * getSpeed(); - motionY = 0; - motionZ = Math.cos(yaw) * getSpeed(); - } else { - motionX = getSpeed(); - motionY = 0; - motionZ = 0; - } + startPreviewFiringDefault(); } /** * Start the slicer moving (exit charging mode). */ public void startMoving(EntityLivingBase target) { - setCharging(false); - snapToPlayerLookVector(); - startX = posX; - startY = posY; - startZ = posZ; - - Entity owner = getOwnerEntity(); - if (target != null) { - double dx = target.posX - posX; - double dy = (target.posY + target.getEyeHeight()) - posY; - double dz = target.posZ - posZ; - double len = Math.sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0) { - motionX = (dx / len) * getSpeed(); - motionY = (dy / len) * getSpeed(); - motionZ = (dz / len) * getSpeed(); - } - } else if (owner != null) { - float yaw = (float) Math.toRadians(owner.rotationYaw); - float pitch = (float) Math.toRadians(owner.rotationPitch); - motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed(); - motionY = -Math.sin(pitch) * getSpeed(); - motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed(); - } + startMovingTowardTargetDefault(target); } @Override @@ -196,15 +127,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { } // Wall collision - if (this.isCollidedHorizontally || this.isCollidedVertically) { - if (!worldObj.isRemote) { - hasHit = true; - if (isExplosive()) { - doExplosion(); - } - } - this.setDead(); - } + handleSolidCollisionTermination(); } @Override @@ -223,19 +146,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { } private void checkBlockCollision() { - if (piercing) return; // Piercing slicers pass through blocks - - Vec3 currentPos = Vec3.createVectorHelper(posX, posY, posZ); - Vec3 nextPos = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ); - MovingObjectPosition blockHit = worldObj.func_147447_a(currentPos, nextPos, false, true, false); - - if (blockHit != null && blockHit.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { - if (!worldObj.isRemote) { - EventHooks.onEnergyProjectileBlockImpact(this, blockHit.blockX, blockHit.blockY, blockHit.blockZ); - } - hasHit = true; - this.setDead(); - } + handleBlockImpact(rayTraceBlocks(posX, posY, posZ, posX + motionX, posY + motionY, posZ + motionZ), false); } private void checkEntityCollision() { @@ -246,29 +157,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { posX + halfW, posY + halfH, posZ + halfW ); - @SuppressWarnings("unchecked") - List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); - - for (EntityLivingBase entity : entities) { - if (shouldIgnoreEntity(entity)) continue; - if (hitEntities.contains(entity.getEntityId())) continue; // Already hit this entity - - if (piercing) { - // Pierce: damage but keep flying - applyDamage(entity); - hitEntities.add(entity.getEntityId()); - } else { - // Non-piercing: damage and die - hasHit = true; - if (isExplosive()) { - doExplosion(); - } else { - applyDamage(entity); - } - this.setDead(); - return; - } - } + processEntitiesInHitBox(hitBox, posX, posY, posZ); } /** @@ -315,14 +204,6 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { this.sliceThickness = thickness; } - public boolean isPiercing() { - return piercing; - } - - public void setPiercing(boolean piercing) { - this.piercing = piercing; - } - // ==================== NBT ==================== @Override @@ -330,7 +211,6 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { readChargingNBT(nbt); this.sliceWidth = sanitize(nbt.hasKey("SliceWidth") ? nbt.getFloat("SliceWidth") : 3.0f, 3.0f, MAX_ENTITY_SIZE); this.sliceThickness = sanitize(nbt.hasKey("SliceThickness") ? nbt.getFloat("SliceThickness") : 0.15f, 0.15f, MAX_ENTITY_SIZE); - this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing"); this.targetSliceWidth = sanitize(nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth, sliceWidth, MAX_ENTITY_SIZE); this.targetSliceThickness = sanitize(nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness, sliceThickness, MAX_ENTITY_SIZE); } @@ -340,7 +220,6 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { writeChargingNBT(nbt); nbt.setFloat("SliceWidth", sliceWidth); nbt.setFloat("SliceThickness", sliceThickness); - nbt.setBoolean("Piercing", piercing); nbt.setFloat("TargetSliceWidth", targetSliceWidth); nbt.setFloat("TargetSliceThickness", targetSliceThickness); } diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index 8e784b5b8..991b2115d 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -36,6 +36,9 @@ import kamkeel.npcs.network.packets.data.npc.WeaponNpcPacket; import kamkeel.npcs.network.packets.data.script.ScriptOverlayClosePacket; import kamkeel.npcs.network.packets.data.script.ScriptOverlayDataPacket; 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.telegraph.TelegraphRemovePacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import kamkeel.npcs.network.packets.player.AuctionActionPacket; @@ -585,6 +588,9 @@ public class PacketHandler { // Data | Ability Packets DATA_PACKET.registerPacket(new TelegraphSpawnPacket()); DATA_PACKET.registerPacket(new TelegraphRemovePacket()); + DATA_PACKET.registerPacket(new EnergyChargeSpawnPacket()); + DATA_PACKET.registerPacket(new EnergyChargeRemovePacket()); + DATA_PACKET.registerPacket(new EnergyExplosionSpawnPacket()); DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket()); DATA_PACKET.registerPacket(new PlayerAbilityStatePacket()); DATA_PACKET.registerPacket(new AbilityHotbarSyncPacket()); diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java index ec351a2c9..6d9a6f598 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumDataPacket.java @@ -70,4 +70,11 @@ public enum EnumDataPacket { // Auction System AUCTION_DATA, + + // Energy Charge Preview + ENERGY_CHARGE_SPAWN, + ENERGY_CHARGE_REMOVE, + + // Energy Explosion Preview + ENERGY_EXPLOSION_SPAWN, } diff --git a/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java b/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java index 256a38299..5d62b1bcf 100644 --- a/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/data/ability/PlayerAbilityStatePacket.java @@ -22,6 +22,7 @@ import java.io.IOException; * bit 1: rotation locked (freeze yaw/pitch) * bit 2: has ability movement (ability controls motion — suppress WASD but don't zero) * bit 3: position locked (freeze position) + * bit 5: ability is in ACTIVE phase */ public final class PlayerAbilityStatePacket extends AbstractPacket { public static final String packetName = "Data|PlayerAbilityState"; @@ -31,6 +32,7 @@ public final class PlayerAbilityStatePacket extends AbstractPacket { public static final byte FLAG_HAS_ABILITY_MOVEMENT = 4; public static final byte FLAG_POSITION_LOCKED = 8; public static final byte FLAG_WAS_FLYING_AT_LOCK = 16; + public static final byte FLAG_ACTIVE_PHASE = 32; private byte flags; private float lockedYaw; diff --git a/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeRemovePacket.java b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeRemovePacket.java new file mode 100644 index 000000000..e9a3a4db6 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeRemovePacket.java @@ -0,0 +1,60 @@ +package kamkeel.npcs.network.packets.data.energycharge; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; +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.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; + +import java.io.IOException; + +/** + * Removes a client-only charging preview entity. + */ +public final class EnergyChargeRemovePacket extends AbstractPacket { + public static final String packetName = "Data|EnergyChargeRemove"; + + private String instanceId; + + public EnergyChargeRemovePacket() { + } + + public EnergyChargeRemovePacket(String instanceId) { + this.instanceId = instanceId; + } + + @Override + public Enum getType() { + return EnumDataPacket.ENERGY_CHARGE_REMOVE; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.DATA_PACKET; + } + + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, instanceId); + } + + @SideOnly(Side.CLIENT) + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + String id = ByteBufUtils.readString(in); + if (id != null && EnergyChargePreviewManager.ClientInstance != null) { + EnergyChargePreviewManager.ClientInstance.removePreview(id); + } + } + + public static void sendToTracking(String instanceId, Entity trackingEntity) { + if (instanceId == null || trackingEntity == null) return; + PacketHandler.Instance.sendTracking(new EnergyChargeRemovePacket(instanceId), trackingEntity); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeSpawnPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeSpawnPacket.java new file mode 100644 index 000000000..16957aaaa --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/data/energycharge/EnergyChargeSpawnPacket.java @@ -0,0 +1,108 @@ +package kamkeel.npcs.network.packets.data.energycharge; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; +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; +import java.lang.reflect.Constructor; + +/** + * Spawns a client-only charging preview entity from serialized spawn data. + */ +public final class EnergyChargeSpawnPacket extends AbstractPacket { + public static final String packetName = "Data|EnergyChargeSpawn"; + + private String instanceId; + private String entityClassName; + private NBTTagCompound spawnNbt; + + public EnergyChargeSpawnPacket() { + } + + public EnergyChargeSpawnPacket(String instanceId, String entityClassName, NBTTagCompound spawnNbt) { + this.instanceId = instanceId; + this.entityClassName = entityClassName; + this.spawnNbt = spawnNbt; + } + + @Override + public Enum getType() { + return EnumDataPacket.ENERGY_CHARGE_SPAWN; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.DATA_PACKET; + } + + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, instanceId); + ByteBufUtils.writeString(out, entityClassName); + ByteBufUtils.writeNBT(out, spawnNbt); + } + + @SideOnly(Side.CLIENT) + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + String id = ByteBufUtils.readString(in); + String className = ByteBufUtils.readString(in); + NBTTagCompound nbt = ByteBufUtils.readNBT(in); + + if (id == null || className == null || nbt == null) return; + if (EnergyChargePreviewManager.ClientInstance == null) return; + + World world = Minecraft.getMinecraft().theWorld; + if (world == null) return; + + EntityEnergyProjectile preview = createPreviewEntity(className, world); + if (preview == null) return; + + preview.setPreviewMode(true); + preview.importSpawnNBT(nbt); + + // Ensure the preview follows current owner immediately on first render frame. + Entity owner = preview.getOwnerEntity(); + if (owner instanceof net.minecraft.entity.EntityLivingBase) { + preview.setPreviewOwner((net.minecraft.entity.EntityLivingBase) owner); + } + + EnergyChargePreviewManager.ClientInstance.addPreview(id, preview); + } + + @SideOnly(Side.CLIENT) + private static EntityEnergyProjectile createPreviewEntity(String className, World world) { + try { + Class clazz = Class.forName(className); + if (!EntityEnergyProjectile.class.isAssignableFrom(clazz)) { + return null; + } + + Constructor constructor = clazz.getConstructor(World.class); + Object instance = constructor.newInstance(world); + return (EntityEnergyProjectile) instance; + } catch (Exception ignored) { + return null; + } + } + + public static void sendToTracking(String instanceId, EntityEnergyProjectile previewEntity, Entity trackingEntity) { + if (previewEntity == null || trackingEntity == null) return; + String className = previewEntity.getClass().getName(); + NBTTagCompound nbt = previewEntity.exportSpawnNBT(); + PacketHandler.Instance.sendTracking(new EnergyChargeSpawnPacket(instanceId, className, nbt), trackingEntity); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/data/energyexplosion/EnergyExplosionSpawnPacket.java b/src/main/java/kamkeel/npcs/network/packets/data/energyexplosion/EnergyExplosionSpawnPacket.java new file mode 100644 index 000000000..e227b8562 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/data/energyexplosion/EnergyExplosionSpawnPacket.java @@ -0,0 +1,74 @@ +package kamkeel.npcs.network.packets.data.energyexplosion; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.entity.EntityEnergyExplosion; +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; + +/** + * Spawns a client-side explosion visual entity from serialized spawn data. + */ +public final class EnergyExplosionSpawnPacket extends AbstractPacket { + public static final String packetName = "Data|EnergyExplosionSpawn"; + + private String instanceId; + private NBTTagCompound spawnNbt; + + public EnergyExplosionSpawnPacket() { + } + + public EnergyExplosionSpawnPacket(String instanceId, NBTTagCompound spawnNbt) { + this.instanceId = instanceId; + this.spawnNbt = spawnNbt; + } + + @Override + public Enum getType() { + return EnumDataPacket.ENERGY_EXPLOSION_SPAWN; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.DATA_PACKET; + } + + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, instanceId); + ByteBufUtils.writeNBT(out, spawnNbt); + } + + @SideOnly(Side.CLIENT) + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + ByteBufUtils.readString(in); // Keep wire compatibility for future instance tracking. + NBTTagCompound nbt = ByteBufUtils.readNBT(in); + + if (nbt == null) return; + + World world = Minecraft.getMinecraft().theWorld; + if (world == null) return; + + EntityEnergyExplosion preview = new EntityEnergyExplosion(world); + preview.importSpawnNBT(nbt); + world.spawnEntityInWorld(preview); + } + + public static void sendToTracking(String instanceId, EntityEnergyExplosion previewEntity, Entity trackingEntity) { + if (instanceId == null || previewEntity == null || trackingEntity == null) return; + NBTTagCompound nbt = previewEntity.exportSpawnNBT(); + PacketHandler.Instance.sendTracking(new EnergyExplosionSpawnPacket(instanceId, nbt), trackingEntity); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java b/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java index 7a4e183a2..6b6262e8b 100644 --- a/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/player/ability/AbilityHotbarSelectPacket.java @@ -1,9 +1,6 @@ package kamkeel.npcs.network.packets.player.ability; import io.netty.buffer.ByteBuf; -import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; @@ -11,7 +8,6 @@ import kamkeel.npcs.network.enums.EnumPlayerPacket; import kamkeel.npcs.util.ByteBufUtils; import net.minecraft.entity.player.EntityPlayer; import noppes.npcs.controllers.PlayerDataController; -import noppes.npcs.controllers.data.AbilityHotbarData; import noppes.npcs.controllers.data.PlayerData; import java.io.IOException; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java index da8ca9d9f..2bcc48601 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilityGetPacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java index ca9925bbf..e4564e8f0 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/ability/ChainedAbilitySavePacket.java @@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.controllers.AbilityController; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketHandler; diff --git a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java index 6a1a17c76..2da72ac7c 100644 --- a/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java +++ b/src/main/java/kamkeel/npcs/util/AnchorPointHelper.java @@ -1,7 +1,7 @@ package kamkeel.npcs.util; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.data.energy.EnergyAnchorData; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.Vec3; diff --git a/src/main/java/kamkeel/npcs/util/FileNameHelper.java b/src/main/java/kamkeel/npcs/util/FileNameHelper.java new file mode 100644 index 000000000..6248600fb --- /dev/null +++ b/src/main/java/kamkeel/npcs/util/FileNameHelper.java @@ -0,0 +1,97 @@ +package kamkeel.npcs.util; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Utility helpers for filename-safe ability/chained names. + * Allowed characters are letters, digits, underscore, and dash. + */ +public final class FileNameHelper { + + private static final Set WINDOWS_RESERVED_NAMES = new HashSet(Arrays.asList( + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" + )); + + private FileNameHelper() { + } + + /** + * Sanitize a name to a filename-safe identifier. + * Whitespace becomes underscores; unsupported chars are removed. + */ + public static String sanitizeName(String input, String fallback) { + String sanitized = sanitizeInternal(input); + if (sanitized.isEmpty()) { + sanitized = sanitizeInternal(fallback); + } + if (sanitized.isEmpty()) { + sanitized = "Ability"; + } + if (isReservedWindowsName(sanitized)) { + sanitized = "_" + sanitized; + } + return sanitized; + } + + /** + * Sanitize textfield input without forcing a fallback. + */ + public static String sanitizeTextInput(String input) { + return sanitizeInternal(input); + } + + /** + * Quick check for compatibility with sanitized identifier rules. + */ + public static boolean isFileNameCompatible(String value) { + if (value == null || value.isEmpty()) return false; + if (isReservedWindowsName(value)) return false; + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (!Character.isLetterOrDigit(c) && c != '_' && c != '-') { + return false; + } + } + return true; + } + + /** + * Human-readable fallback display text for sanitized names. + */ + public static String toDisplayName(String value) { + if (value == null || value.isEmpty()) return ""; + return value.replace('_', ' '); + } + + private static String sanitizeInternal(String raw) { + if (raw == null) return ""; + String trimmed = raw.trim(); + if (trimmed.isEmpty()) return ""; + + StringBuilder out = new StringBuilder(trimmed.length()); + + for (int i = 0; i < trimmed.length(); i++) { + char c = trimmed.charAt(i); + if (Character.isLetterOrDigit(c)) { + out.append(c); + } else if (c == '_' || c == '-') { + out.append(c); + } else if (Character.isWhitespace(c)) { + if (out.length() == 0) continue; + char last = out.charAt(out.length() - 1); + if (last == '_') continue; + out.append('_'); + } + } + + return out.toString(); + } + + private static boolean isReservedWindowsName(String value) { + return WINDOWS_RESERVED_NAMES.contains(value.toUpperCase()); + } +} diff --git a/src/main/java/kamkeel/npcs/util/Register.java b/src/main/java/kamkeel/npcs/util/Register.java index 75184778d..a01ef84c1 100644 --- a/src/main/java/kamkeel/npcs/util/Register.java +++ b/src/main/java/kamkeel/npcs/util/Register.java @@ -3,7 +3,7 @@ package kamkeel.npcs.util; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.IEffectAction; +import kamkeel.npcs.controllers.data.ability.data.effect.IEffectAction; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import noppes.npcs.LogWriter; import noppes.npcs.controllers.AnimationController; diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index b63481c21..92103a102 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -2,11 +2,11 @@ package noppes.npcs; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.ability.ConcurrentSlot; -import kamkeel.npcs.controllers.data.ability.ToggleEntry; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; @@ -268,6 +268,15 @@ public abstract class AbstractDataAbilities { return currentAbility != null && currentAbility.isExecuting(); } + /** + * True when an executing ability grants invulnerability for its current phase. + */ + public boolean isCurrentAbilityInvulnerable() { + return currentAbility != null + && currentAbility.isExecuting() + && currentAbility.isInvulnerableForCurrentPhase(); + } + /** * Get the currently executing ability. */ @@ -296,7 +305,7 @@ public abstract class AbstractDataAbilities { /** * Currently active toggles. Key = ability key, Value = toggle entry with state and tick counter. */ - protected Map activeToggles = new LinkedHashMap<>(); + protected Map activeToggles = new LinkedHashMap<>(); /** * Cycle a toggle ability to its next state. @@ -306,7 +315,7 @@ public abstract class AbstractDataAbilities { * @return The new state (0 = off, 1+ = active state number) */ public int toggleAbility(String key) { - ToggleEntry existing = activeToggles.get(key); + AbilityToggleEntry existing = activeToggles.get(key); if (existing != null) { int currentState = existing.getState(); int maxStates = existing.getAbility().getToggleStates(); @@ -326,7 +335,7 @@ public abstract class AbstractDataAbilities { * @return 0 if not active, 1+ for active state */ public int getToggleState(String key) { - ToggleEntry entry = activeToggles.get(key); + AbilityToggleEntry entry = activeToggles.get(key); return entry != null ? entry.getState() : 0; } @@ -340,7 +349,7 @@ public abstract class AbstractDataAbilities { } return; } - ToggleEntry existing = activeToggles.get(key); + AbilityToggleEntry existing = activeToggles.get(key); if (existing != null) { int currentState = existing.getState(); if (currentState != state) { @@ -373,7 +382,7 @@ public abstract class AbstractDataAbilities { if (fireToggleEvent(ability, 0, state)) return false; - ToggleEntry entry = new ToggleEntry(ability, state); + AbilityToggleEntry entry = new AbilityToggleEntry(ability, state); activeToggles.put(key, entry); ability.onToggleOn(getEntity()); ability.onToggleStateChanged(getEntity(), 0, state); @@ -381,7 +390,7 @@ public abstract class AbstractDataAbilities { return true; } - private int cycleToggleState(String key, ToggleEntry entry, int oldState, int newState) { + private int cycleToggleState(String key, AbilityToggleEntry entry, int oldState, int newState) { Ability ability = entry.getAbility(); if (newState < 1 || newState > ability.getToggleStates()) return oldState; @@ -394,7 +403,7 @@ public abstract class AbstractDataAbilities { } private void deactivateToggle(String key) { - ToggleEntry entry = activeToggles.get(key); + AbilityToggleEntry entry = activeToggles.get(key); if (entry == null) return; int oldState = entry.getState(); @@ -418,8 +427,8 @@ public abstract class AbstractDataAbilities { EntityLivingBase entity = getEntity(); List toRemove = null; - for (Map.Entry mapEntry : activeToggles.entrySet()) { - ToggleEntry entry = mapEntry.getValue(); + for (Map.Entry mapEntry : activeToggles.entrySet()) { + AbilityToggleEntry entry = mapEntry.getValue(); entry.incrementTick(); if (entry.getAbility().hasActiveToggle()) { @@ -441,7 +450,7 @@ public abstract class AbstractDataAbilities { if (toRemove != null) { for (String key : toRemove) { - ToggleEntry entry = activeToggles.remove(key); + AbilityToggleEntry entry = activeToggles.remove(key); if (entry != null) { int oldState = entry.getState(); entry.getAbility().onToggleOff(entity); @@ -483,8 +492,8 @@ public abstract class AbstractDataAbilities { protected void clearActiveToggles() { if (activeToggles.isEmpty()) return; EntityLivingBase entity = getEntity(); - for (Map.Entry mapEntry : activeToggles.entrySet()) { - ToggleEntry entry = mapEntry.getValue(); + for (Map.Entry mapEntry : activeToggles.entrySet()) { + AbilityToggleEntry entry = mapEntry.getValue(); entry.getAbility().onToggleOff(entity); entry.getAbility().onToggleStateChanged(entity, entry.getState(), 0); } @@ -502,7 +511,7 @@ public abstract class AbstractDataAbilities { Ability ability = AbilityController.Instance != null ? AbilityController.Instance.resolveAbility(key) : null; if (ability != null && ability.isToggleable()) { - activeToggles.put(key, new ToggleEntry(ability, state)); + activeToggles.put(key, new AbilityToggleEntry(ability, state)); } } else { activeToggles.remove(key); diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 4fd5b3fdf..718793ddd 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -122,7 +122,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta6.1") +@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta7") public class CustomNpcs { @SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy") @@ -292,15 +292,15 @@ public class CustomNpcs { registerNewEntity(EntityChairMount.class, "CustomNpcChairMount", 64, 10, false); registerNewEntity(EntityProjectile.class, "throwableitem", 64, 3, true); registerNewEntity(EntityMagicProjectile.class, "magicprojectile", 64, 3, true); - registerNewEntity(EntityAbilityOrb.class, "abilityorb", 64, 3, true); - registerNewEntity(EntityAbilityDisc.class, "abilitydisc", 64, 3, true); - registerNewEntity(EntityAbilityLaser.class, "abilitylaser", 64, 3, true); - registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 64, 3, true); + registerNewEntity(EntityAbilityOrb.class, "abilityorb", 64, 1, true); + registerNewEntity(EntityAbilityDisc.class, "abilitydisc", 64, 1, true); + registerNewEntity(EntityAbilityLaser.class, "abilitylaser", 64, 1, true); + registerNewEntity(EntityAbilityBeam.class, "abilitybeam", 64, 1, true); registerNewEntity(EntityAbilitySweeper.class, "abilitysweeper", 64, 3, true); registerNewEntity(EntityAbilityZone.class, "abilityzone", 64, 3, true); registerNewEntity(EntityAbilityDome.class, "energydome", 64, 3, true); registerNewEntity(EntityAbilityPanel.class, "energypanel", 64, 3, true); - registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 3, true); + registerNewEntity(EntityEnergySlicer.class, "energyslicer", 64, 1, true); new RecipeController(); diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 548939a54..a21eace79 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -3,10 +3,10 @@ package noppes.npcs; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; -import kamkeel.npcs.controllers.data.ability.ToggleEntry; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; @@ -14,7 +14,6 @@ import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; -import net.minecraft.nbt.NBTTagString; import net.minecraft.util.DamageSource; import noppes.npcs.controllers.data.Animation; import noppes.npcs.entity.EntityNPCInterface; @@ -556,12 +555,15 @@ public class DataAbilities extends AbstractDataAbilities { * @return true if ability was interrupted */ public boolean onDamage(DamageSource source, float amount) { - // Track hit for hit count condition - recordHit(); - if (currentAbility == null || !currentAbility.isExecuting()) { return false; } + if (currentAbility.isInvulnerableForCurrentPhase()) { + return false; + } + + // Track hit for hit count condition + recordHit(); net.minecraft.entity.Entity sourceEntity = source.getEntity(); EntityLivingBase attacker = sourceEntity instanceof EntityLivingBase ? (EntityLivingBase) sourceEntity : null; @@ -1095,7 +1097,7 @@ public class DataAbilities extends AbstractDataAbilities { // Active toggles (compound format with state) NBTTagList toggleList = new NBTTagList(); - for (Map.Entry entry : activeToggles.entrySet()) { + for (Map.Entry entry : activeToggles.entrySet()) { NBTTagCompound toggleNbt = new NBTTagCompound(); toggleNbt.setString("Key", entry.getKey()); toggleNbt.setInteger("State", entry.getValue().getState()); diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index 2cadbfd9f..5b5efccbe 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -629,7 +629,12 @@ public class ScriptPlayerEventHandler { // Handle ability damage interactions (Guard counter, damage reduction) PlayerData pData = PlayerData.get((EntityPlayer) event.entityLiving); if (pData != null && pData.abilityData != null && pData.abilityData.isExecutingAbility()) { - event.ammount = pData.abilityData.onDamage(event.source, event.ammount); + if (pData.abilityData.isCurrentAbilityInvulnerable()) { + event.ammount = 0; + cancel = true; + } else { + event.ammount = pData.abilityData.onDamage(event.source, event.ammount); + } } PlayerDataScript handler = ScriptController.Instance.getPlayerScripts((EntityPlayer) event.entityLiving); diff --git a/src/main/java/noppes/npcs/client/ClientAbilityState.java b/src/main/java/noppes/npcs/client/ClientAbilityState.java index c737024fd..fb3d88c6e 100644 --- a/src/main/java/noppes/npcs/client/ClientAbilityState.java +++ b/src/main/java/noppes/npcs/client/ClientAbilityState.java @@ -16,6 +16,7 @@ public class ClientAbilityState { public static boolean hasAbilityMovement = false; public static boolean positionLocked = false; public static boolean wasFlyingAtLock = false; + public static boolean activePhase = false; public static float lockedYaw = 0; public static float lockedPitch = 0; @@ -28,6 +29,7 @@ public class ClientAbilityState { hasAbilityMovement = (flags & PlayerAbilityStatePacket.FLAG_HAS_ABILITY_MOVEMENT) != 0; positionLocked = (flags & PlayerAbilityStatePacket.FLAG_POSITION_LOCKED) != 0; wasFlyingAtLock = (flags & PlayerAbilityStatePacket.FLAG_WAS_FLYING_AT_LOCK) != 0; + activePhase = (flags & PlayerAbilityStatePacket.FLAG_ACTIVE_PHASE) != 0; lockedYaw = yaw; lockedPitch = pitch; } @@ -56,6 +58,7 @@ public class ClientAbilityState { hasAbilityMovement = false; positionLocked = false; wasFlyingAtLock = false; + activePhase = false; lockedYaw = 0; lockedPitch = 0; } diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index 9e91d5556..46eb58efa 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -14,9 +14,12 @@ import kamkeel.npcs.client.renderer.RenderAbilitySweeper; import kamkeel.npcs.client.renderer.RenderAbilityZone; import kamkeel.npcs.client.renderer.RenderAbilityDome; import kamkeel.npcs.client.renderer.RenderAbilityPanel; +import kamkeel.npcs.client.renderer.EnergyChargePreviewRenderer; +import kamkeel.npcs.client.renderer.RenderEnergyExplosion; import kamkeel.npcs.client.renderer.RenderEnergySlicer; import kamkeel.npcs.client.renderer.TelegraphRenderer; import kamkeel.npcs.client.renderer.lightning.LightningHandler; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.entity.EntityAbilityBeam; import kamkeel.npcs.entity.EntityAbilityDisc; @@ -26,6 +29,7 @@ import kamkeel.npcs.entity.EntityAbilitySweeper; import kamkeel.npcs.entity.EntityAbilityZone; import kamkeel.npcs.entity.EntityAbilityDome; import kamkeel.npcs.entity.EntityAbilityPanel; +import kamkeel.npcs.entity.EntityEnergyExplosion; import kamkeel.npcs.entity.EntityEnergySlicer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; @@ -283,6 +287,7 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityAbilityDome.class, new RenderAbilityDome()); RenderingRegistry.registerEntityRenderingHandler(EntityAbilityPanel.class, new RenderAbilityPanel()); RenderingRegistry.registerEntityRenderingHandler(EntityEnergySlicer.class, new RenderEnergySlicer()); + RenderingRegistry.registerEntityRenderingHandler(EntityEnergyExplosion.class, new RenderEnergyExplosion()); RenderingRegistry.registerEntityRenderingHandler(EntityCustomNpc.class, new RenderCustomNpc()); @@ -358,6 +363,10 @@ public class ClientProxy extends CommonProxy { TelegraphManager.initClient(); MinecraftForge.EVENT_BUS.register(new TelegraphRenderer()); + // Energy charge preview rendering system + EnergyChargePreviewManager.initClient(); + MinecraftForge.EVENT_BUS.register(new EnergyChargePreviewRenderer()); + // Lightning effect rendering system MinecraftForge.EVENT_BUS.register(new LightningHandler()); diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index 58d6f5a3f..dbe6e691d 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -8,6 +8,7 @@ import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.relauncher.Side; import kamkeel.npcs.client.renderer.lightning.LightningBolt; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager; import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.data.RequestProperSpawnData; @@ -53,6 +54,7 @@ public class ClientTickHandler { private long buttonTime = 0L; private final int[] ignoreKeys = new int[]{157, 29, 54, 42, 184, 56, 220, 219}; private boolean lastSpecialKeyDown = false; + private boolean wasMovementSuppressed = false; @SubscribeEvent(priority = EventPriority.LOWEST) public void onClientTick(TickEvent.ClientTickEvent event) { @@ -62,6 +64,9 @@ public class ClientTickHandler { ClientCacheHandler.clearCache(); ClientAbilityState.reset(); RequestProperSpawnData.clear(); + if (EnergyChargePreviewManager.ClientInstance != null) { + EnergyChargePreviewManager.ClientInstance.clear(); + } } this.prevWorld = mc.theWorld; } @@ -80,9 +85,15 @@ public class ClientTickHandler { lastSpecialKeyDown = specialKeyDown; } + boolean suppressMovement = mc.currentScreen == null && ClientAbilityState.shouldSuppressMovementInput(); + if (!suppressMovement && wasMovementSuppressed) { + syncMovementKeyStates(mc); + } + wasMovementSuppressed = suppressMovement; + // Suppress player input during ability-controlled phases. // Only suppress when no GUI screen is open (screens already capture input). - if (mc.currentScreen == null && ClientAbilityState.shouldSuppressMovementInput()) { + if (suppressMovement) { // Unpress movement keybinds at the source BEFORE updatePlayerMoveState() reads them. KeyBinding.setKeyBindState(mc.gameSettings.keyBindForward.getKeyCode(), false); KeyBinding.setKeyBindState(mc.gameSettings.keyBindBack.getKeyCode(), false); @@ -164,6 +175,11 @@ public class ClientTickHandler { TelegraphManager.ClientInstance.tick(mc.theWorld); } + // Tick packet-driven energy charge previews + if (EnergyChargePreviewManager.ClientInstance != null) { + EnergyChargePreviewManager.ClientInstance.tick(mc.theWorld); + } + // Update lightning bolts for ability effects LightningBolt.updateAll(); @@ -292,6 +308,27 @@ public class ClientTickHandler { || keyCode == mc.gameSettings.keyBindSprint.getKeyCode(); } + private void syncMovementKeyStates(Minecraft mc) { + syncKeyBindingState(mc.gameSettings.keyBindForward); + syncKeyBindingState(mc.gameSettings.keyBindBack); + syncKeyBindingState(mc.gameSettings.keyBindLeft); + syncKeyBindingState(mc.gameSettings.keyBindRight); + syncKeyBindingState(mc.gameSettings.keyBindJump); + syncKeyBindingState(mc.gameSettings.keyBindSneak); + syncKeyBindingState(mc.gameSettings.keyBindSprint); + } + + private void syncKeyBindingState(KeyBinding keyBinding) { + int keyCode = keyBinding.getKeyCode(); + boolean pressed; + if (keyCode < 0) { + pressed = Mouse.isButtonDown(keyCode + 100); + } else { + pressed = Keyboard.isKeyDown(keyCode); + } + KeyBinding.setKeyBindState(keyCode, pressed); + } + private boolean isIgnoredKey(int key) { int[] var2 = this.ignoreKeys; int var3 = var2.length; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index 85ef3e5d8..23d18ad20 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -4,8 +4,8 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket; import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcGetPacket; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java index 57a363e1f..a70b0318d 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilitySaveConfirm.java @@ -84,8 +84,12 @@ public class SubGuiAbilitySaveConfirm extends SubGuiInterface implements ISubGui @Override public void subGuiClosed(SubGuiInterface subgui) { if (subgui instanceof SubGuiDuplicateNameConfirm) { - if (((SubGuiDuplicateNameConfirm) subgui).isConfirmed()) { + SubGuiDuplicateNameConfirm confirm = (SubGuiDuplicateNameConfirm) subgui; + if (confirm.isConfirmed()) { doSave(); + } else if (confirm.isBack()) { + // Keep this save-confirm dialog open. + return; } else { close(); } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index d1946e2e2..11a194d29 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -4,11 +4,11 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; -import kamkeel.npcs.controllers.data.ability.AbilityIconData; -import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider; -import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; +import kamkeel.npcs.controllers.data.ability.data.AbilityIconData; +import kamkeel.npcs.controllers.data.ability.gui.IChainedAbilityFieldProvider; +import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket; @@ -563,6 +563,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText return true; case 2: // Up if (entryIndex > 0) { + // Commit any focused delay field before reordering + GuiNpcTextField.unfocus(); ChainedAbilityEntry entry = entries.remove(entryIndex); entries.add(entryIndex - 1, entry); // Can't be concurrent at position 0 @@ -574,12 +576,16 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText return true; case 3: // Down if (entryIndex < entries.size() - 1) { + // Commit any focused delay field before reordering + GuiNpcTextField.unfocus(); ChainedAbilityEntry entry = entries.remove(entryIndex); entries.add(entryIndex + 1, entry); initGui(); } return true; case 4: // Delete + // Commit any focused delay field before removing + GuiNpcTextField.unfocus(); entries.remove(entryIndex); initGui(); return true; diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java index b16386c32..5d316f416 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiDuplicateNameConfirm.java @@ -10,7 +10,13 @@ import noppes.npcs.client.gui.util.SubGuiInterface; */ public class SubGuiDuplicateNameConfirm extends SubGuiInterface { - private boolean confirmed = false; + public enum Result { + CONTINUE, + BACK, + CANCEL + } + + private Result result = Result.CANCEL; public SubGuiDuplicateNameConfirm() { setBackground("menubg.png"); @@ -26,22 +32,38 @@ public class SubGuiDuplicateNameConfirm extends SubGuiInterface { addLabel(new GuiNpcLabel(0, "ability.duplicate.name", guiLeft + 10, y)); y += 30; - addButton(new GuiNpcButton(0, guiLeft + 20, y, 80, 20, "gui.continue")); - addButton(new GuiNpcButton(1, guiLeft + 120, y, 80, 20, "gui.cancel")); + addButton(new GuiNpcButton(0, guiLeft + 10, y, 65, 20, "gui.continue")); + addButton(new GuiNpcButton(1, guiLeft + 78, y, 65, 20, "gui.back")); + addButton(new GuiNpcButton(2, guiLeft + 146, y, 65, 20, "gui.cancel")); } @Override public void buttonEvent(GuiButton guibutton) { if (guibutton.id == 0) { - confirmed = true; + result = Result.CONTINUE; close(); } else if (guibutton.id == 1) { - confirmed = false; + result = Result.BACK; + close(); + } else if (guibutton.id == 2) { + result = Result.CANCEL; close(); } } + public Result getResult() { + return result; + } + public boolean isConfirmed() { - return confirmed; + return result == Result.CONTINUE; + } + + public boolean isBack() { + return result == Result.BACK; + } + + public boolean isCancelled() { + return result == Result.CANCEL; } } diff --git a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java index 8d7d88d61..64b81624d 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java +++ b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java @@ -245,6 +245,9 @@ public class GuiFieldBuilder { case STRING: { String sVal = def.getValue() != null ? def.getValue().toString() : ""; GuiNpcTextField tf = new GuiNpcTextField(widgetId, parent, fontRenderer, fieldX, y, fieldW, 20, sVal); + if ("gui.name".equals(def.getLabel())) { + tf.setFileNameSafe(); + } if (!def.isEnabled()) tf.setEnabled(false); if (hover != null) tf.setHoverText(hover); sw.addTextField(tf); diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java index f6562e7a5..64be9e499 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageAbilities.java @@ -3,7 +3,7 @@ package noppes.npcs.client.gui.global; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityVariant; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket; import kamkeel.npcs.network.packets.request.ability.ChainedAbilitiesGetPacket; @@ -24,8 +24,8 @@ import noppes.npcs.client.gui.advanced.SubGuiAbilityTypeSelect; import noppes.npcs.client.gui.advanced.SubGuiAbilityVariantSelect; import noppes.npcs.client.gui.advanced.SubGuiChainedAbilityConfig; import noppes.npcs.client.gui.advanced.SubGuiDuplicateNameConfirm; -import noppes.npcs.client.gui.util.AbilityPreviewExecutor; -import noppes.npcs.client.gui.util.GuiAbilityInterface; +import kamkeel.npcs.controllers.data.ability.preview.AbilityPreviewExecutor; +import kamkeel.npcs.controllers.data.ability.gui.GuiAbilityInterface; import noppes.npcs.client.gui.util.GuiCustomScroll; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; @@ -104,7 +104,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface // ==================== PENDING SAVE ==================== private Ability pendingSaveAbility = null; - private boolean pendingNewCreation = false; // ==================== PENDING VARIANT ==================== private String pendingTypeId = null; @@ -511,13 +510,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface Ability newAbility = AbilityController.Instance.create(pendingTypeId); if (newAbility != null) { gui.getVariants().get(idx).apply(newAbility); - if (hasDuplicateName(newAbility)) { - pendingSaveAbility = newAbility; - pendingNewCreation = true; - pendingTypeId = null; - setSubGui(new SubGuiDuplicateNameConfirm()); - return true; - } pendingTypeId = null; openConfig(newAbility); return true; @@ -544,12 +536,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface if (variants.size() == 1) { variants.get(0).apply(newAbility); } - if (hasDuplicateName(newAbility)) { - pendingSaveAbility = newAbility; - pendingNewCreation = true; - setSubGui(new SubGuiDuplicateNameConfirm()); - return true; - } openConfig(newAbility); return true; } @@ -561,27 +547,22 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface * @return true if a SubGui was opened (caller should return early, skipping initGui) */ private boolean handleDuplicateNameClosed(SubGuiDuplicateNameConfirm gui) { - if (gui.isConfirmed() && pendingSaveAbility != null) { - if (pendingNewCreation) { - openConfig(pendingSaveAbility); - pendingSaveAbility = null; - pendingNewCreation = false; - return true; - } else { - PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); - pendingSaveAbility = null; - } - } else if (pendingSaveAbility != null) { - if (pendingNewCreation) { - pendingSaveAbility = null; - pendingNewCreation = false; - } else { - Ability ability = pendingSaveAbility; - pendingSaveAbility = null; - setSubGui(ability.createConfigGui(this)); - return true; - } + if (pendingSaveAbility == null) return false; + + if (gui.isConfirmed()) { + PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT())); + pendingSaveAbility = null; + return false; } + + if (gui.isBack()) { + Ability ability = pendingSaveAbility; + pendingSaveAbility = null; + setSubGui(ability.createConfigGui(this)); + return true; + } + + pendingSaveAbility = null; return false; } @@ -590,7 +571,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface */ private boolean handlePendingSave() { if (hasDuplicateName(pendingSaveAbility)) { - pendingNewCreation = false; setSubGui(new SubGuiDuplicateNameConfirm()); return true; } diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java index 4b3be2d6a..eaadbbc7b 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java @@ -4,11 +4,12 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSelectPacket; import net.minecraft.client.Minecraft; import noppes.npcs.client.ClientProxy; +import noppes.npcs.client.ClientAbilityState; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.ScaledResolution; @@ -189,6 +190,7 @@ public class AbilityHotbarComponent extends HudComponent { int centerX = overlayWidth / 2; int centerY = overlayHeight / 2; + boolean activePhase = ClientAbilityState.activePhase; // Compute text visibility: 0=Shown, 1=Hidden, 2=Held boolean showText; @@ -246,16 +248,16 @@ public class AbilityHotbarComponent extends HudComponent { if (rawSlotIndex == -1) { // Deselect slot — draw empty marker - drawDeselectSlot(cx, cy, scaledSize, isCenter, slotAlpha); + drawDeselectSlot(cx, cy, scaledSize, isCenter, slotAlpha, activePhase); } else { - slots[rawSlotIndex].drawCarousel(mc, sr, slotCooldown, cx, cy, scaledSize, isCenter, slotAlpha, showText); + slots[rawSlotIndex].drawCarousel(mc, sr, slotCooldown, cx, cy, scaledSize, isCenter, slotAlpha, showText, activePhase); } } GL11.glPopMatrix(); } - private void drawDeselectSlot(int cx, int cy, int size, boolean isCenter, float alpha) { + private void drawDeselectSlot(int cx, int cy, int size, boolean isCenter, float alpha, boolean activePhase) { float radius = size / 2f; float baseAlpha = isCenter ? 0.9f : 0.6f; float a = baseAlpha * alpha; @@ -268,7 +270,13 @@ public class AbilityHotbarComponent extends HudComponent { GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - if (isCenter) { + if (activePhase) { + if (isCenter) { + GL11.glColor4f(0.65f, 0.14f, 0.14f, a); + } else { + GL11.glColor4f(0.38f, 0.10f, 0.10f, a); + } + } else if (isCenter) { GL11.glColor4f(0.5f, 0.2f, 0.2f, a); } else { GL11.glColor4f(0.3f, 0.3f, 0.3f, a); @@ -292,7 +300,13 @@ public class AbilityHotbarComponent extends HudComponent { } // Draw X mark - if (isCenter) { + if (activePhase) { + if (isCenter) { + GL11.glColor4f(1.0f, 0.45f, 0.35f, alpha); + } else { + GL11.glColor4f(0.75f, 0.30f, 0.30f, alpha * 0.8f); + } + } else if (isCenter) { GL11.glColor4f(0.8f, 0.4f, 0.4f, alpha); } else { GL11.glColor4f(0.5f, 0.5f, 0.5f, alpha * 0.8f); diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java index c91ad016b..ab55c9560 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarSlotRenderer.java @@ -3,8 +3,8 @@ package noppes.npcs.client.gui.hud.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; @@ -48,7 +48,7 @@ public class AbilityHotbarSlotRenderer extends Gui { public void drawCarousel(Minecraft mc, ScaledResolution sr, float cooldownProgress, int cx, int cy, int size, boolean isCenter, float slotAlpha, - boolean showText) { + boolean showText, boolean activePhase) { float nameAlpha = this.nameAlpha; boolean isHorizontal = ConfigClient.AbilityHotbarHorizontal; if (size <= 0 || slotAlpha <= 0) return; @@ -61,21 +61,54 @@ public class AbilityHotbarSlotRenderer extends Gui { boolean altTexture = ConfigClient.AbilityHotbarAltTexture; - if (altTexture) { - drawRoundedRect(-radius, -radius, radius, radius, 0.6f, 0.6f, 0.6f, 0.75f * alpha, false); + float fillR; + float fillG; + float fillB; + float outlineR; + float outlineG; + float outlineB; + + if (activePhase) { if (isCenter) { - drawRoundedRect(-radius, -radius, radius, radius, 0.8f, 0.8f, 0.2f, alpha, true); + fillR = 0.95f; + fillG = 0.26f; + fillB = 0.22f; + outlineR = 1.0f; + outlineG = 0.52f; + outlineB = 0.28f; } else { - drawRoundedRect(-radius, -radius, radius, radius, 0.4f, 0.4f, 0.4f, alpha * 0.8f, true); + fillR = 0.36f; + fillG = 0.14f; + fillB = 0.14f; + outlineR = 0.65f; + outlineG = 0.22f; + outlineB = 0.20f; } + } else if (isCenter) { + fillR = 0.8f; + fillG = 0.8f; + fillB = 0.2f; + outlineR = 0.8f; + outlineG = 0.8f; + outlineB = 0.2f; } else { - if (isCenter) { - drawCircle(radius, 0.8f, 0.8f, 0.2f, 0.66f * alpha, false); - drawCircle(radius, 0.8f, 0.8f, 0.2f, alpha, true); - } else { - drawCircle(radius, 1f, 1f, 1f, 0.66f * alpha, false); - drawCircle(radius, 0.4f, 0.4f, 0.4f, alpha * 0.8f, true); - } + fillR = 0.4f; + fillG = 0.4f; + fillB = 0.4f; + outlineR = 1f; + outlineG = 1f; + outlineB = 1f; + } + + if (altTexture) { + float baseR = activePhase ? 0.30f : 0.6f; + float baseG = activePhase ? 0.12f : 0.6f; + float baseB = activePhase ? 0.12f : 0.6f; + drawRoundedRect(-radius, -radius, radius, radius, baseR, baseG, baseB, 0.75f * alpha, false); + drawRoundedRect(-radius, -radius, radius, radius, fillR, fillG, fillB, alpha * 0.8f, true); + } else { + drawCircle(radius, outlineR, outlineG, outlineB, 0.66f * alpha, false); + drawCircle(radius, fillR, fillG, fillB, alpha * 0.8f, true); } // Ability icon diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java index 1371bcc2a..c36d06de2 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityIcon.java @@ -3,9 +3,9 @@ package noppes.npcs.client.gui.hud.ability; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityIconData; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.AbilityIconData; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; import net.minecraft.client.renderer.Tessellator; diff --git a/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java b/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java index 1d0180463..36def1d43 100644 --- a/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java @@ -2,8 +2,8 @@ package noppes.npcs.client.gui.player.inventory; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSavePacket; import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSelectPacket; diff --git a/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java b/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java index a3d85f7e4..dfef9e88b 100644 --- a/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java +++ b/src/main/java/noppes/npcs/client/gui/player/inventory/SubGuiSelectAbilityForSlot.java @@ -2,7 +2,7 @@ package noppes.npcs.client.gui.player.inventory; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import net.minecraft.client.gui.GuiButton; import noppes.npcs.client.ClientCacheHandler; import noppes.npcs.client.gui.util.GuiCustomScroll; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java b/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java index 8c8eca60d..9d972be3d 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiMenuTopButton.java @@ -96,7 +96,10 @@ public class GuiMenuTopButton extends GuiNpcButton { @Override public boolean mousePressed(Minecraft minecraft, int i, int j) { - boolean bo = !active && getVisible() && hover; + int h = this.height - (active ? 0 : 2); + boolean bo = !active && getVisible() + && i >= xPosition && j >= yPosition + && i < xPosition + getWidth() && j < yPosition + h; if (bo && listener != null) { listener.actionPerformed(this); return false; diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java index ffe6718ee..1d82deb61 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcTextField.java @@ -1,6 +1,7 @@ package noppes.npcs.client.gui.util; import kamkeel.npcs.util.TextSplitter; +import kamkeel.npcs.util.FileNameHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiScreen; @@ -26,6 +27,7 @@ public class GuiNpcTextField extends GuiTextField { public float minFloat = 0, maxFloat = Float.MAX_VALUE, defFloat = 0; protected static GuiNpcTextField activeTextfield = null; public boolean canEdit = true; + private boolean fileNameSafe = false; public String hoverableText = ""; private boolean wasHovered = false; private int hoverCount = 0; @@ -71,6 +73,11 @@ public class GuiNpcTextField extends GuiTextField { public boolean textboxKeyTyped(char c, int i) { if (!canEdit || !isFocused()) return false; + + if (fileNameSafe && !GuiScreen.isCtrlKeyDown() && !isFileNameCharAllowed(c, i)) { + return false; + } + // Allow Ctrl shortcuts (select all, copy, paste, cut) to bypass char filter if (GuiScreen.isCtrlKeyDown()) { // For Ctrl+V (paste) on numeric fields, filter clipboard to valid chars @@ -87,7 +94,17 @@ public class GuiNpcTextField extends GuiTextField { } return true; } - return super.textboxKeyTyped(c, i); + if (i == 47 && fileNameSafe) { + String clipboard = GuiScreen.getClipboardString(); + if (clipboard != null) { + String filtered = filterFileNameClipboard(clipboard); + if (!filtered.isEmpty()) + writeText(filtered); + } + return true; + } + boolean handled = super.textboxKeyTyped(c, i); + return handled; } if (!charAllowed(c, i)) return false; @@ -179,6 +196,9 @@ public class GuiNpcTextField extends GuiTextField { else if (getFloat() > maxFloat) setText(maxFloat + ""); } + if (fileNameSafe) { + normalizeFileNameText(); + } setCursorPositionZero(); if (listener != null) listener.unFocused(this); @@ -238,6 +258,51 @@ public class GuiNpcTextField extends GuiTextField { return this; } + public GuiNpcTextField setFileNameSafe() { + fileNameSafe = true; + normalizeFileNameText(); + return this; + } + + private boolean isSpecialNavigationKey(int keyCode) { + for (int allowed : allowedSpecialChars) { + if (allowed == keyCode) { + return true; + } + } + return false; + } + + private boolean isFileNameCharAllowed(char c, int keyCode) { + if (isSpecialNavigationKey(keyCode) || c < 32) { + return true; + } + return Character.isLetterOrDigit(c) || c == '_' || c == '-' || c == ' '; + } + + private String filterFileNameClipboard(String clipboard) { + StringBuilder filtered = new StringBuilder(clipboard.length()); + for (int idx = 0; idx < clipboard.length(); idx++) { + char c = clipboard.charAt(idx); + if (Character.isLetterOrDigit(c) || c == '_' || c == '-') { + filtered.append(c); + } else if (Character.isWhitespace(c)) { + filtered.append(' '); + } + } + return filtered.toString(); + } + + private void normalizeFileNameText() { + String current = getText(); + String sanitized = FileNameHelper.sanitizeTextInput(current); + if (sanitized.equals(current)) return; + + int cursor = getCursorPosition(); + setText(sanitized); + setCursorPosition(Math.min(cursor, sanitized.length())); + } + public void setHoverText(String text) { this.hoverableText = StatCollector.translateToLocal(text); } diff --git a/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java b/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java index f714ea2c7..4584b7072 100644 --- a/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java +++ b/src/main/java/noppes/npcs/client/gui/util/IChainedAbilityConfigCallback.java @@ -1,6 +1,6 @@ package noppes.npcs.client.gui.util; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; /** * Callback for when a chained ability is saved from the config SubGui. diff --git a/src/main/java/noppes/npcs/client/model/ModelMPM.java b/src/main/java/noppes/npcs/client/model/ModelMPM.java index 7a8273a77..76bf0e591 100644 --- a/src/main/java/noppes/npcs/client/model/ModelMPM.java +++ b/src/main/java/noppes/npcs/client/model/ModelMPM.java @@ -422,7 +422,7 @@ public class ModelMPM extends ModelNPCMale { Minecraft.getMinecraft().entityRenderer.disableLightmap((double) 0); } - GL11.glColor4f(1.0F, 1.0F, 1.0F, overlayData.getAlpha()); + glColor(overlayData.getColor(), overlayData.getAlpha()); GL11.glDepthMask(!npc.isInvisible()); @@ -522,6 +522,14 @@ public class ModelMPM extends ModelNPCMale { } + private static void glColor(int color, float alpha) { + float r = ((color >> 16) & 0xFF) / 255.0F; + float g = ((color >> 8) & 0xFF) / 255.0F; + float b = (color & 0xFF) / 255.0F; + + GL11.glColor4f(r, g, b, alpha); + } + @Override public void setRotationAngles(float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, diff --git a/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java b/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java index 7f532c2ad..d1042628e 100644 --- a/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java +++ b/src/main/java/noppes/npcs/client/renderer/RenderCNPCPlayer.java @@ -79,7 +79,7 @@ public class RenderCNPCPlayer extends RenderPlayer { RenderHelper.disableStandardItemLighting(); } - GL11.glColor4f(1.0F, 1.0F, 1.0F, overlayData.alpha); + glColor(overlayData.getColor(), overlayData.getAlpha()); GL11.glDepthMask(!player.isInvisible()); @@ -97,6 +97,14 @@ public class RenderCNPCPlayer extends RenderPlayer { return true; } + private static void glColor(int color, float alpha) { + float r = ((color >> 16) & 0xFF) / 255.0F; + float g = ((color >> 8) & 0xFF) / 255.0F; + float b = (color & 0xFF) / 255.0F; + + GL11.glColor4f(r, g, b, alpha); + } + public void postRenderOverlay() { GL11.glPopMatrix(); diff --git a/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java b/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java index 2d1d1f258..f3c91d547 100644 --- a/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java +++ b/src/main/java/noppes/npcs/client/renderer/RenderNPCInterface.java @@ -482,7 +482,7 @@ public class RenderNPCInterface extends RenderLiving { Minecraft.getMinecraft().entityRenderer.disableLightmap((double) 0); } - GL11.glColor4f(1.0F, 1.0F, 1.0F, overlayData.getAlpha()); + glColor(overlayData.getColor(), overlayData.getAlpha()); GL11.glDepthMask(!npc.isInvisible()); @@ -521,6 +521,14 @@ public class RenderNPCInterface extends RenderLiving { } } + private static void glColor(int color, float alpha) { + float r = ((color >> 16) & 0xFF) / 255.0F; + float g = ((color >> 8) & 0xFF) / 255.0F; + float b = (color & 0xFF) / 255.0F; + + GL11.glColor4f(r, g, b, alpha); + } + @Override protected float handleRotationFloat(EntityLivingBase par1EntityLiving, float par2) { EntityNPCInterface npc = (EntityNPCInterface) par1EntityLiving; diff --git a/src/main/java/noppes/npcs/config/ConfigClient.java b/src/main/java/noppes/npcs/config/ConfigClient.java index 3616627f4..79a647026 100644 --- a/src/main/java/noppes/npcs/config/ConfigClient.java +++ b/src/main/java/noppes/npcs/config/ConfigClient.java @@ -17,6 +17,7 @@ public class ConfigClient { public final static String MODEL = "Model"; public final static String TEXTURE = "Texture"; public final static String HUD = "Hud"; + public final static String RENDERING = "Rendering"; /** @@ -52,6 +53,10 @@ public class ConfigClient { public static Property HideEffectsBarProperty; public static boolean HideEffectsBar = false; + // Rendering Properties + public static Property LowResExplosionProperty; + public static boolean LowResExplosion = false; + /** * Questing Properties **/ @@ -277,6 +282,14 @@ public class ConfigClient { HideEffectsBarProperty = config.get(VISUAL, "Hide Effects Bar", false, "Hides CNPC+ Inventory Effects Bar"); HideEffectsBar = HideEffectsBarProperty.getBoolean(false); + LowResExplosionProperty = config.get( + RENDERING, + "Low Res Explosion", + false, + "Disables most Energy Explosion voxel rendering and keeps particle effects only." + ); + LowResExplosion = LowResExplosionProperty.getBoolean(false); + DialogSpeedProperty = config.get(VISUAL, "Dialog Speed", true, "Only set for gradual dialogs"); DialogSpeed = DialogSpeedProperty.getInt(10); diff --git a/src/main/java/noppes/npcs/config/ConfigEnergy.java b/src/main/java/noppes/npcs/config/ConfigEnergy.java index f3b36a948..197bb4968 100644 --- a/src/main/java/noppes/npcs/config/ConfigEnergy.java +++ b/src/main/java/noppes/npcs/config/ConfigEnergy.java @@ -10,8 +10,14 @@ public class ConfigEnergy { public static Configuration config; public final static String DOMES = "Domes"; + public final static String EXPLOSIONS = "Explosions"; public static String[] DomeItemBlacklist; + /** + * Server-side toggle for energy projectile terrain destruction. + * Disabled by default to avoid block grief. + */ + public static boolean EnableEnergyExplosionBlockDamage; public static void init(File configFile) { config = new Configuration(configFile); @@ -26,6 +32,13 @@ public class ConfigEnergy { + "Format: modid:itemname (e.g., minecraft:ender_pearl)" ); + EnableEnergyExplosionBlockDamage = config.getBoolean( + "Enable Energy Explosion Block Damage", EXPLOSIONS, + false, + "If true, explosive ENERGY projectile abilities can destroy terrain.\n" + + "This is intended for server-side use and is disabled by default." + ); + } catch (Exception e) { FMLLog.log(Level.ERROR, e, "CustomNPC+ has had a problem loading its energy configuration"); } finally { diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 79890baeb..fa439578e 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -2,10 +2,10 @@ package noppes.npcs.controllers.data; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.controllers.data.ability.AbilityPhase; -import kamkeel.npcs.controllers.data.ability.ChainedAbility; -import kamkeel.npcs.controllers.data.ability.IAbilityAction; -import kamkeel.npcs.controllers.data.ability.ToggleEntry; +import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; +import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; +import kamkeel.npcs.controllers.data.ability.data.IAbilityAction; +import kamkeel.npcs.controllers.data.ability.data.entry.AbilityToggleEntry; import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.ability.AbilityCooldownSyncPacket; @@ -23,7 +23,6 @@ import net.minecraft.util.DamageSource; import noppes.npcs.AbstractDataAbilities; import noppes.npcs.EventHooks; import noppes.npcs.LogWriter; -import noppes.npcs.NoppesUtilPlayer; import noppes.npcs.api.ability.IPlayerAbilityData; import noppes.npcs.api.entity.IPlayer; import noppes.npcs.controllers.ScriptController; @@ -807,6 +806,9 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA if (currentAbility == null || !currentAbility.isExecuting()) { return amount; } + if (currentAbility.isInvulnerableForCurrentPhase()) { + return 0; + } net.minecraft.entity.Entity sourceEntity = source.getEntity(); EntityLivingBase attacker = sourceEntity instanceof EntityLivingBase ? (EntityLivingBase) sourceEntity : null; @@ -938,6 +940,9 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA if (wasFlyingAtLock) { flags |= PlayerAbilityStatePacket.FLAG_WAS_FLYING_AT_LOCK; } + if (currentAbility.getPhase() == AbilityPhase.ACTIVE) { + flags |= PlayerAbilityStatePacket.FLAG_ACTIVE_PHASE; + } return flags; } @@ -995,7 +1000,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA // Active toggles (compound format with state) NBTTagList toggleList = new NBTTagList(); - for (Map.Entry entry : activeToggles.entrySet()) { + for (Map.Entry entry : activeToggles.entrySet()) { NBTTagCompound toggleNbt = new NBTTagCompound(); toggleNbt.setString("Key", entry.getKey()); toggleNbt.setInteger("State", entry.getValue().getState()); diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java index f062d891c..abfea96e3 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityHotbarData.java @@ -2,7 +2,6 @@ package noppes.npcs.controllers.data; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; -import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.packets.data.ability.AbilityHotbarSyncPacket; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; diff --git a/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java b/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java index 4b0cde375..01c236999 100644 --- a/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java +++ b/src/main/java/noppes/npcs/controllers/data/SkinOverlay.java @@ -10,8 +10,10 @@ public class SkinOverlay implements ISkinOverlay { public boolean glow = true; public boolean blend = true; + public float alpha = 1.0F; public float size = 1.0F; + public int color = 0xFFFFFF; public float speedX = 0.0F; public float speedY = 0.0F; @@ -78,6 +80,15 @@ public class SkinOverlay implements ISkinOverlay { return size; } + public void setColor(int color) { + this.color = Math.max(0, color); + this.updateClient(); + } + + public int getColor() { + return color; + } + public void setTextureScale(float scaleX, float scaleY) { this.scaleX = scaleX; this.scaleY = scaleY; @@ -131,6 +142,11 @@ public class SkinOverlay implements ISkinOverlay { this.blend = compound.getBoolean("SkinOverlayBlend"); this.alpha = compound.getFloat("SkinOverlayAlpha"); this.size = compound.getFloat("SkinOverlaySize"); + if(compound.hasKey("SkinOverlayColor")){ + this.color = compound.getInteger("SkinOverlayColor"); + } else { + this.color = 0xFFFFFF; + } this.speedX = compound.getFloat("SkinOverlaySpeedX"); this.speedY = compound.getFloat("SkinOverlaySpeedY"); this.scaleX = compound.getFloat("SkinOverlayScaleX"); @@ -146,6 +162,7 @@ public class SkinOverlay implements ISkinOverlay { compound.setBoolean("SkinOverlayBlend", this.getBlend()); compound.setFloat("SkinOverlayAlpha", this.getAlpha()); compound.setFloat("SkinOverlaySize", this.getSize()); + compound.setInteger("SkinOverlayColor", this.getColor()); compound.setFloat("SkinOverlaySpeedX", this.getSpeedX()); compound.setFloat("SkinOverlaySpeedY", this.getSpeedY()); compound.setFloat("SkinOverlayScaleX", this.getTextureScaleX()); diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index 4834504b3..d6ae75a6f 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -659,6 +659,9 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti if (damagesource.damageType != null && damagesource.damageType.equals("outOfWorld") && isKilled()) { reset(); } + if (this.abilities != null && this.abilities.isCurrentAbilityInvulnerable()) { + return false; + } // Check for custom weapon attack speed - bypass immunity if enough time has passed Entity sourceEntity = damagesource.getEntity(); @@ -1834,12 +1837,15 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti try { readSpawnData(ByteBufUtils.readNBT(buf)); } catch (IOException e) { - RequestProperSpawnData.reportMissingData(this); + if(this.worldObj != null && this.worldObj.isRemote){ + RequestProperSpawnData.reportMissingData(this); + } } } public void readSpawnData(NBTTagCompound compound) { - if ((compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) { + if (this.worldObj != null && this.worldObj.isRemote && + (compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) { RequestProperSpawnData.reportMissingData(this); return; } diff --git a/src/main/java/noppes/npcs/items/ItemStaff.java b/src/main/java/noppes/npcs/items/ItemStaff.java index 5891df2e1..1f9a45381 100644 --- a/src/main/java/noppes/npcs/items/ItemStaff.java +++ b/src/main/java/noppes/npcs/items/ItemStaff.java @@ -1,13 +1,13 @@ package noppes.npcs.items; -import kamkeel.npcs.controllers.data.ability.AnchorPoint; -import kamkeel.npcs.controllers.data.ability.data.EnergyAnchorData; -import kamkeel.npcs.controllers.data.ability.data.EnergyCombatData; -import kamkeel.npcs.controllers.data.ability.data.EnergyDisplayData; -import kamkeel.npcs.controllers.data.ability.data.EnergyHomingData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLifespanData; -import kamkeel.npcs.controllers.data.ability.data.EnergyLightningData; -import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData; +import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; +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; +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.controllers.data.ability.data.energy.EnergyTrajectoryData; import kamkeel.npcs.entity.EntityAbilityOrb; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java index 38a95db0c..de9c8036e 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyProjectile.java @@ -161,6 +161,22 @@ public class ScriptEnergyProjectile extends Sc entity.setExplosionDamageFalloff(falloff); } + public int getHitType() { + return entity.getHitType(); + } + + public void setHitType(int hitType) { + entity.setHitType(hitType); + } + + public int getMultiHitDelayTicks() { + return entity.getMultiHitDelayTicks(); + } + + public void setMultiHitDelayTicks(int delayTicks) { + entity.setMultiHitDelayTicks(delayTicks); + } + // ==================== MOVEMENT ==================== public float getSpeed() { diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java index f0105cdd9..f120350c4 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergySlicer.java @@ -45,14 +45,6 @@ public class ScriptEnergySlicer extends ScriptEner entity.setSliceThickness(thickness); } - public boolean isPiercing() { - return entity.isPiercing(); - } - - public void setPiercing(boolean piercing) { - entity.setPiercing(piercing); - } - // ==================== FIRE ==================== @Override diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index dbe0442cd..90d877011 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -167,6 +167,7 @@ gui.unused=Unused gui.apply=Apply gui.cancel=Cancel gui.continue=Continue +gui.back=Back gui.yes=Yes gui.abilities=Abilities gui.toggles=Toggles @@ -1783,6 +1784,11 @@ gui.applyPreset=Apply Preset ability.radius=Radius ability.knockback=Knockback ability.knockbackUp=Knockback Up +ability.hitType=Hit Type +ability.hitType.single=Single +ability.hitType.pierce=Pierce +ability.hitType.multi=Multi +ability.multiHitDelay=Multi-Hit Delay ability.distance=Distance ability.maxDistance=Max Distance ability.lifetime=Lifetime @@ -1928,6 +1934,12 @@ ability.hover.burstReplay=Replay windup and active animations for each burst ite ability.save.confirm=Save ability preset ability.save.overwrite=Overwrite existing preset ability.duplicate.name=A preset with this name already exists +ability.invulnerable=Invulnerable +ability.invulnerable.none=None +ability.invulnerable.windup=Windup +ability.invulnerable.active=Active +ability.invulnerable.both=Both +ability.hover.invulnerable=Ignores all incoming damage and hit reactions during selected phases. Windup/Both also disable interrupts. ability.load.select=Select ability to load: ability.load.mode=How should this ability be loaded? ability.clone=Clone @@ -1985,6 +1997,8 @@ ability.hover.windUpColor=Color of telegraph during charge-up phase ability.hover.activeColor=Color of telegraph when ability is active ability.hover.homing=Projectile tracks toward target ability.hover.homingStr=How aggressively the projectile tracks (higher = tighter turns) +ability.hover.hitType=Single: stops on first hit. Pierce: can hit each entity once. Multi: can re-hit entities using the delay below. +ability.hover.multiHitDelay=Ticks between repeat hits on the same entity when Hit Type is Multi (20 ticks = 1 second) ability.hover.explosive=Creates explosion on impact ability.hover.explosionRad=Size of explosion radius ability.hover.piercing=Hits multiple targets instead of stopping on first hit @@ -2064,8 +2078,6 @@ ability.validFor=Valid For ability.userType.NPC_ONLY=NPCs ability.userType.PLAYER_ONLY=Players ability.userType.BOTH=Both -ability.dieOnImpact=Die on Impact - ability.icon.section.uv=UV Coordinates ability.icon.x=Icon X ability.icon.y=Icon Y