Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
Kam committed 2026-02-22 16:46:18 -05:00
commit c0ce53a814
150 files changed
+3376 -1479

No files matched your search

+1 -1
View File
@@ -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"
+1 -1
Submodule src/api updated: a9c77d20eb...658da549ee.
@@ -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);
}
}
}
@@ -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();
@@ -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();
@@ -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();
@@ -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();
@@ -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();
@@ -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;
@@ -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.
@@ -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;
}
}
@@ -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();
@@ -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;
@@ -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<String, String> migratedAbilityNames = new HashMap<String, String>();
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<String, ?> 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<String, ?> 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<String, String> 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<String, String> 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<String> getCustomAbilityNames() {
@@ -445,9 +516,10 @@ public class AbilityController implements IAbilityHandler {
// CHAINED ABILITIES
// ═══════════════════════════════════════════════════════════════════════════
private void loadChainedAbilities() {
private void loadChainedAbilities(Map<String, String> 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
@@ -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;
}
@@ -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;
@@ -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 &amp; 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.
*/
@@ -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;
/**
@@ -1,5 +1,6 @@
package kamkeel.npcs.controllers.data.ability;
import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase;
import net.minecraft.entity.EntityLivingBase;
/**
@@ -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;
}
}
@@ -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;
@@ -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;
@@ -1,7 +1,5 @@
package kamkeel.npcs.controllers.data.ability.conditions;
import kamkeel.npcs.controllers.data.ability.UserType;
public enum ConditionFilter {
CASTER,
TARGET,
@@ -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;
@@ -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;
@@ -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;
@@ -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 &amp; 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;
@@ -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;
@@ -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;
/**
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability.data;
package kamkeel.npcs.controllers.data.ability.data.energy;
import net.minecraft.nbt.NBTTagCompound;
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability.data;
package kamkeel.npcs.controllers.data.ability.data.energy;
import net.minecraft.nbt.NBTTagCompound;
@@ -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;
}
}
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability.data;
package kamkeel.npcs.controllers.data.ability.data.energy;
import net.minecraft.nbt.NBTTagCompound;
@@ -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;
@@ -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;
@@ -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);
@@ -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;
/**
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability;
package kamkeel.npcs.controllers.data.ability.enums;
public enum AbilityPhase {
IDLE, // Not executing
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability;
package kamkeel.npcs.controllers.data.ability.enums;
/**
* Defines anchor points for ability charging effects.
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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];
}
@@ -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 {
/**
@@ -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.
@@ -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).
@@ -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.
@@ -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;
/**
@@ -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;
@@ -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;
@@ -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)
*/
@@ -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;
@@ -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;
@@ -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;
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability;
package kamkeel.npcs.controllers.data.ability.preview;
import net.minecraft.entity.Entity;
@@ -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
@@ -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;
@@ -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<FieldDef> defs) {
@@ -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;
@@ -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
@@ -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;
@@ -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;
@@ -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);
}
// ═══════════════════════════════════════════════════════════════════
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
}
@@ -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;
@@ -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<EntityAbilityBeam> 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<EntityAbilityBeam> 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";
@@ -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<EntityAbilityDisc> 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<EntityAbilityDisc> 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";
@@ -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 = "";
@@ -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;
@@ -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<E extends EntityEnergyProjectile>
// 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<E extends EntityEnergyProjectile>
/**
* 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<E extends EntityEnergyProjectile>
// ==================== 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<E extends EntityEnergyProjectile>
@Override
public void detach() {
entities = null;
projectileSpawned = null;
spawnedCount = 0;
}
@Override
@@ -274,21 +350,31 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
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<E extends EntityEnergyProjectile>
// 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<E extends EntityEnergyProjectile>
}
}
// 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<E extends EntityEnergyProjectile>
@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<E extends EntityEnergyProjectile>
}
}
entities = null;
projectileSpawned = null;
spawnedCount = 0;
chargeVisualIds = null;
chargeVisualCaster = null;
}
// ==================== TELEGRAPH ====================
@@ -557,6 +668,22 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
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<E extends EntityEnergyProjectile>
@SideOnly(Side.CLIENT)
@Override
public final void getAbilityDefinitions(List<FieldDef> 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);
@@ -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<EntityAbilityLaser> implements IAbilityLaserShot {
@@ -34,12 +31,11 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
private float laserWidth = 0.3f;
private float expansionSpeed = 3.0f;
private int lingerTicks = 8;
private boolean dieOnImpact = false;
public AbilityLaserShot() {
super(
new EnergyDisplayData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 0.0f),
new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f),
new EnergyCombatData(6.0f, 0.5f, 0.05f, false, 2.0f, 0.5f, HitType.PIERCE, 5),
new EnergyHomingData(),
new EnergyLifespanData(150.0f, 100)
);
@@ -50,9 +46,9 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
this.minRange = 3.0f;
this.cooldownTicks = 0;
this.windUpTicks = 15;
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
this.lockMovement = LockMode.WINDUP_AND_ACTIVE;
this.rotationMode = RotationMode.TRACK;
this.rotationPhase = LockMovementType.WINDUP;
this.rotationPhase = LockMode.WINDUP;
this.telegraphType = TelegraphType.LINE;
this.showTelegraph = true;
this.windUpAnimationName = "Ability_Laser_Windup";
@@ -123,7 +119,6 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
laserWidth,
resolved, combatData, lightningData, lifespanData, trajectoryData,
expansionSpeed, lingerTicks);
laser.setDieOnImpact(dieOnImpact);
return laser;
}
@@ -141,7 +136,7 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
@Override
protected void setupEntityPreview(EntityAbilityLaser laser, EntityLivingBase caster,
EnergyDisplayData resolved, ProjectileData projData, int index) {
laser.setupPreview(caster, laserWidth, resolved, lightningData, expansionSpeed, lifespanData.maxDistance);
laser.setupPreview(caster, laserWidth, resolved, lightningData, projData.anchor, windUpTicks, expansionSpeed, lifespanData.maxDistance);
}
@Override
@@ -161,7 +156,6 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
nbt.setFloat("laserWidth", laserWidth);
nbt.setFloat("expansionSpeed", expansionSpeed);
nbt.setInteger("lingerTicks", lingerTicks);
nbt.setBoolean("dieOnImpact", dieOnImpact);
}
@Override
@@ -169,7 +163,6 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
this.laserWidth = nbt.getFloat("laserWidth");
this.expansionSpeed = nbt.getFloat("expansionSpeed");
this.lingerTicks = nbt.getInteger("lingerTicks");
this.dieOnImpact = nbt.getBoolean("dieOnImpact");
}
// ==================== TYPE-SPECIFIC GETTERS ====================
@@ -198,14 +191,6 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
this.lingerTicks = lingerTicks;
}
public boolean isDieOnImpact() {
return dieOnImpact;
}
public void setDieOnImpact(boolean dieOnImpact) {
this.dieOnImpact = dieOnImpact;
}
// ==================== TYPE-SPECIFIC GUI ====================
@SideOnly(Side.CLIENT)
@@ -226,7 +211,6 @@ public class AbilityLaserShot extends AbilityEnergyProjectile<EntityAbilityLaser
FieldDef.intField("ability.lifetime", this::getMaxLifetime, this::setMaxLifetime).range(1, 1200)
));
defs.add(FieldDef.floatField("ability.maxDistance", this::getMaxDistance, this::setMaxDistance).range(1.0f, 500.0f));
defs.add(FieldDef.boolField("ability.dieOnImpact", this::isDieOnImpact, this::setDieOnImpact));
defs.add(FieldDef.section("ability.section.explosive"));
defs.add(FieldDef.boolField("gui.enabled", this::isExplosive, this::setExplosive).hover("ability.hover.explosive"));
defs.add(FieldDef.floatField("gui.radius", this::getExplosionRadius, this::setExplosionRadius).visibleWhen(this::isExplosive));
@@ -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;
@@ -44,7 +44,7 @@ public class AbilityOrb extends AbilityEnergyProjectile<EntityAbilityOrb> 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<EntityAbilityOrb> 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);
@@ -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 = "";
@@ -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<EntityEnergySlicer> {
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<EntityEnergySlicer> {
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<EntityEnergySlicer> {
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<EntityEnergySlicer> {
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<EntityEnergySlicer> {
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<EntityEnergySlicer> {
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)
@@ -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
@@ -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 = "";
@@ -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;
@@ -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<String, EntityEnergyProjectile> previews = new ConcurrentHashMap<String, EntityEnergyProjectile>();
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<EntityEnergyProjectile> 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<EntityEnergyProjectile> 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();
}
}
}
}
@@ -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;
@@ -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<Vec3> 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<Integer> 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<Vec3>(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.
* <p>
* 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<Vec3>(trailPoints.subList(trailStartIndex, trailPoints.size()));
trailPointAges = new ArrayList<Integer>(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<EntityLivingBase> 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<Vec3> 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<Integer> 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);
}
@@ -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<EntityLivingBase> 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);
}
/**
@@ -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;
@@ -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.
* <p>
* 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<Integer> 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;
}
}
@@ -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<EntityLivingBase> 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 ====================
@@ -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;
@@ -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;
@@ -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;
Loaded 100 of 150 files, more files were not shown because too many files have changed in this diff. Show more