mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 18:57:32 +00:00
Fix Firing Energy Projectiles and Make Charging State purely visual
This commit is contained in:
1 parent
3a8f829de9
commit
18968bf7c7
18 files changed
+1086
-439
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+169
-59
@@ -15,6 +15,8 @@ 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;
|
||||
@@ -55,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 ====================
|
||||
|
||||
@@ -77,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);
|
||||
@@ -209,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +326,8 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
@Override
|
||||
public void detach() {
|
||||
entities = null;
|
||||
projectileSpawned = null;
|
||||
spawnedCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,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();
|
||||
@@ -297,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,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
|
||||
@@ -392,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) {
|
||||
@@ -410,6 +516,10 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
}
|
||||
}
|
||||
entities = null;
|
||||
projectileSpawned = null;
|
||||
spawnedCount = 0;
|
||||
chargeVisualIds = null;
|
||||
chargeVisualCaster = null;
|
||||
}
|
||||
|
||||
// ==================== TELEGRAPH ====================
|
||||
|
||||
+1
-1
@@ -139,7 +139,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
|
||||
|
||||
+68
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import net.minecraft.world.World;
|
||||
import noppes.npcs.LogWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -31,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;
|
||||
@@ -39,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<>();
|
||||
|
||||
@@ -65,6 +67,46 @@ 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 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.
|
||||
*
|
||||
@@ -89,9 +131,8 @@ 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;
|
||||
@@ -118,7 +159,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
this.chargeTick = 0;
|
||||
this.chargeOffsetDistance = chargeOffsetDistance;
|
||||
this.anchorData = anchor;
|
||||
this.fadeTrail = !attachedToOwner;
|
||||
this.motionX = 0;
|
||||
this.motionY = 0;
|
||||
this.motionZ = 0;
|
||||
@@ -160,10 +200,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
this.prevHeadOffsetY = 0;
|
||||
this.prevHeadOffsetZ = 0;
|
||||
|
||||
// Attach to owner for anchor following
|
||||
this.attachedToOwner = true;
|
||||
this.renderTailOrb = true;
|
||||
|
||||
// Clear motion
|
||||
this.motionX = 0;
|
||||
this.motionY = 0;
|
||||
@@ -174,12 +210,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
* 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;
|
||||
beginLookVectorLaunch(true);
|
||||
|
||||
// Reset head offset to origin
|
||||
headOffsetX = 0;
|
||||
@@ -189,51 +220,21 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
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;
|
||||
|
||||
// Reset trail
|
||||
trailPoints.clear();
|
||||
trailPointAges.clear();
|
||||
resetTrailStorage();
|
||||
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
|
||||
if (isFadingMode()) trailPointAges.add(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;
|
||||
}
|
||||
// Fire along look vector for parity with real launch behavior.
|
||||
setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 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;
|
||||
beginLookVectorLaunch(false);
|
||||
|
||||
// Head starts at the origin (tail position)
|
||||
headOffsetX = 0;
|
||||
@@ -243,36 +244,13 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
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;
|
||||
|
||||
// Initialize trail with just the origin point
|
||||
trailPoints.clear();
|
||||
trailPointAges.clear();
|
||||
resetTrailStorage();
|
||||
trailPoints.add(Vec3.createVectorHelper(0, 0, 0));
|
||||
if (fadeTrail) trailPointAges.add(0);
|
||||
if (isFadingMode()) 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();
|
||||
}
|
||||
// Calculate velocity toward target when provided; otherwise along look vector.
|
||||
setMotionTowardTargetOrLookVector(target, startX, startY, startZ, getSpeed(), getSpeed(), 0, 0);
|
||||
|
||||
if (DEBUG_LOGGING && !worldObj.isRemote) {
|
||||
LogWriter.info("[Beam] startFiring: origin=(" + startX + "," + startY + "," + startZ +
|
||||
@@ -310,7 +288,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
prevHeadOffsetZ = headOffsetZ;
|
||||
|
||||
// Age trail points for fading effect
|
||||
if (fadeTrail) {
|
||||
if (isFadingMode()) {
|
||||
ageTrailPoints();
|
||||
}
|
||||
|
||||
@@ -344,7 +322,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;
|
||||
@@ -372,7 +350,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
|
||||
@@ -418,25 +396,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;
|
||||
@@ -451,12 +447,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);
|
||||
}
|
||||
@@ -474,7 +470,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;
|
||||
@@ -586,7 +582,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
}
|
||||
|
||||
public void setAttachedToOwner(boolean attached) {
|
||||
this.attachedToOwner = attached;
|
||||
setBeamMode(modeFromAnchored(attached));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,7 +605,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() {
|
||||
@@ -628,28 +630,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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -671,17 +679,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++) {
|
||||
@@ -701,10 +718,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
|
||||
@@ -714,16 +728,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);
|
||||
@@ -733,9 +749,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);
|
||||
}
|
||||
|
||||
@@ -137,31 +137,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
* 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;
|
||||
}
|
||||
beginLookVectorLaunch(true);
|
||||
setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,35 +146,8 @@ 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();
|
||||
}
|
||||
beginLookVectorLaunch(false);
|
||||
setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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.EnergyLifespanData;
|
||||
@@ -8,7 +9,6 @@ import kamkeel.npcs.controllers.data.ability.data.EnergyTrajectoryData;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
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;
|
||||
@@ -156,13 +156,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
|
||||
@@ -180,71 +179,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);
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
// Set initial direction from owner's rotation
|
||||
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)
|
||||
@@ -253,6 +232,20 @@ 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() {
|
||||
MovingObjectPosition blockHit = rayTraceBlocks(startX, startY, startZ, endX, endY, endZ);
|
||||
|
||||
@@ -342,8 +335,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
|
||||
@@ -561,6 +552,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
|
||||
@@ -605,10 +614,11 @@ 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) {
|
||||
EnergyLightningData lightning, EnergyAnchorData anchor, int chargeDuration,
|
||||
float expansionSpeed, float maxDistance) {
|
||||
this.setPreviewMode(true);
|
||||
this.setPreviewOwner(owner);
|
||||
|
||||
@@ -619,28 +629,37 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
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 at the configured anchor, matching other energy projectiles.
|
||||
setupCharging(anchor, chargeDuration);
|
||||
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;
|
||||
this.endX = pos.xCoord;
|
||||
this.endY = pos.yCoord;
|
||||
this.endZ = pos.zCoord;
|
||||
|
||||
// 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.getLookVec();
|
||||
if (look == 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)
|
||||
);
|
||||
}
|
||||
this.dirX = look.xCoord;
|
||||
this.dirY = look.yCoord;
|
||||
this.dirZ = look.zCoord;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.LogWriter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Orb projectile - spherical homing energy ball.
|
||||
@@ -22,6 +25,10 @@ import net.minecraft.world.World;
|
||||
* 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;
|
||||
private static final boolean DEBUG_ORB_FIRE_TRACE = false;
|
||||
private static final int DEBUG_TRACE_TICKS = 14;
|
||||
|
||||
public EntityAbilityOrb(World world) {
|
||||
super(world);
|
||||
@@ -71,7 +78,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
|
||||
this.renderCurrentSize = 0.01f;
|
||||
this.prevRenderSize = 0.01f;
|
||||
|
||||
// Initial position at anchor point
|
||||
// Initial position follows anchor helper during charging.
|
||||
Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData);
|
||||
this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord);
|
||||
this.prevPosX = pos.xCoord;
|
||||
@@ -91,32 +98,8 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
|
||||
* 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;
|
||||
}
|
||||
beginLookVectorLaunch(true);
|
||||
setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,35 +107,28 @@ 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
|
||||
// Launch strictly along owner's look vector using shared launch origin.
|
||||
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();
|
||||
if (owner instanceof EntityLivingBase) {
|
||||
Vec3 look = getOwnerLookVector();
|
||||
if (look != null) {
|
||||
debugTrace("startMoving:before", look, -1.0f);
|
||||
if (beginLookVectorLaunch((EntityLivingBase) owner, look, false)) {
|
||||
debugTrace("setLaunchPosition", look, -1.0f);
|
||||
}
|
||||
motionX = look.xCoord * getSpeed();
|
||||
motionY = look.yCoord * getSpeed();
|
||||
motionZ = look.zCoord * getSpeed();
|
||||
debugTrace("startMoving:after", look, -1.0f);
|
||||
return;
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
|
||||
// Fallback if owner is unavailable.
|
||||
setCharging(false);
|
||||
syncStartPositionToCurrent();
|
||||
setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0);
|
||||
debugTrace("startMoving:fallback", null, -1.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,13 +146,17 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
|
||||
}
|
||||
|
||||
if (worldObj.isRemote) {
|
||||
if (shouldDebugTick()) debugTrace("client:beforeInterpolation", null, -1.0f);
|
||||
handleClientInterpolation();
|
||||
if (shouldDebugTick()) debugTrace("client:afterInterpolation", null, -1.0f);
|
||||
} else {
|
||||
// Server-side logic
|
||||
if (shouldDebugTick()) debugTrace("server:beforeMovement", null, -1.0f);
|
||||
updateMovement();
|
||||
checkBlockCollision();
|
||||
checkEntityCollision();
|
||||
this.moveEntity(motionX, motionY, motionZ);
|
||||
if (shouldDebugTick()) debugTrace("server:afterMove", null, -1.0f);
|
||||
}
|
||||
|
||||
// Check wall collision
|
||||
@@ -197,10 +177,28 @@ 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) {
|
||||
if (shouldDebugTick()) debugTrace("updateMovement:homingFull", null, 1.0f);
|
||||
updateHoming();
|
||||
} else {
|
||||
float ramp = homingTicks / (float) HOMING_RAMP_TICKS;
|
||||
if (shouldDebugTick()) debugTrace("updateMovement:homingRamp", null, ramp);
|
||||
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
|
||||
@@ -224,12 +222,54 @@ public class EntityAbilityOrb extends EntityEnergyProjectile {
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
processEntitiesInHitBox(hitBox, posX, posY, posZ);
|
||||
processEntitiesInHitBox(hitBox, nextX, nextY, nextZ);
|
||||
}
|
||||
|
||||
private boolean shouldDebugTick() {
|
||||
return DEBUG_ORB_FIRE_TRACE && !isCharging() && !previewMode && ticksExisted <= DEBUG_TRACE_TICKS;
|
||||
}
|
||||
|
||||
private void debugTrace(String stage, Vec3 look, float ramp) {
|
||||
if (!DEBUG_ORB_FIRE_TRACE || worldObj == null) return;
|
||||
|
||||
String side = worldObj.isRemote ? "CLIENT" : "SERVER";
|
||||
Entity owner = getOwnerEntity();
|
||||
String ownerInfo = owner == null
|
||||
? "null"
|
||||
: owner.getEntityId() + "@(" + fmt(owner.posX) + "," + fmt(owner.posY) + "," + fmt(owner.posZ) + ")";
|
||||
String lookInfo = look == null
|
||||
? "null"
|
||||
: "(" + fmt(look.xCoord) + "," + fmt(look.yCoord) + "," + fmt(look.zCoord) + ")";
|
||||
|
||||
LogWriter.info(
|
||||
"[OrbFireTrace][" + side + "][" + stage + "] "
|
||||
+ "id=" + getEntityId()
|
||||
+ " tick=" + ticksExisted
|
||||
+ " charging=" + isCharging()
|
||||
+ " start=(" + fmt(startX) + "," + fmt(startY) + "," + fmt(startZ) + ")"
|
||||
+ " pos=(" + fmt(posX) + "," + fmt(posY) + "," + fmt(posZ) + ")"
|
||||
+ " prev=(" + fmt(prevPosX) + "," + fmt(prevPosY) + "," + fmt(prevPosZ) + ")"
|
||||
+ " motion=(" + fmt(motionX) + "," + fmt(motionY) + "," + fmt(motionZ) + ")"
|
||||
+ " size=" + fmt(size)
|
||||
+ " speed=" + fmt(getSpeed())
|
||||
+ " ramp=" + (ramp < 0 ? "n/a" : fmt(ramp))
|
||||
+ " owner=" + ownerInfo
|
||||
+ " look=" + lookInfo
|
||||
);
|
||||
}
|
||||
|
||||
private static String fmt(double v) {
|
||||
return String.format(Locale.ROOT, "%.4f", v);
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
@@ -83,6 +83,10 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
}
|
||||
}
|
||||
|
||||
public void setChargeDuration(int duration) {
|
||||
this.chargeDuration = duration;
|
||||
}
|
||||
|
||||
public float getChargeProgress() {
|
||||
if (chargeDuration <= 0) return 1.0f;
|
||||
return Math.min(1.0f, (float) chargeTick / chargeDuration);
|
||||
@@ -333,6 +337,24 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
readEnergyBaseNBT(nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export spawn NBT for non-world preview rendering sync.
|
||||
*/
|
||||
public final NBTTagCompound exportSpawnNBT() {
|
||||
NBTTagCompound nbt = new NBTTagCompound();
|
||||
writeSpawnNBT(nbt);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import spawn NBT for non-world preview rendering sync.
|
||||
*/
|
||||
public final void importSpawnNBT(NBTTagCompound nbt) {
|
||||
if (nbt != null) {
|
||||
readSpawnNBT(nbt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buffer) {
|
||||
try {
|
||||
|
||||
@@ -516,27 +516,237 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
// ==================== LAUNCH HELPERS ====================
|
||||
|
||||
/**
|
||||
* For player casters, snap the projectile to a position along the player's look vector.
|
||||
* This ensures projectiles launch aligned with the crosshair rather than from the hand anchor.
|
||||
* Only affects player casters; NPC projectiles launch from their anchor position as configured.
|
||||
* Snaps the projectile position to the owner's look vector on launch.
|
||||
* This ensures projectiles launch aligned with the owner's facing direction
|
||||
* rather than from the last anchor point position.
|
||||
* Should be called in startMoving/startFiring BEFORE setting startX/Y/Z.
|
||||
*/
|
||||
protected void snapToPlayerLookVector() {
|
||||
protected void snapToLookVector() {
|
||||
setLookVectorLaunchPosition(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared launch transition: exit charging, snap to look-vector launch origin,
|
||||
* sync start position, and optionally reset interpolation state.
|
||||
*
|
||||
* @param resetInterpolationState true to reset prev/lastTick/interp state to launch origin.
|
||||
* @return true when look-vector launch origin was resolved from owner/look data.
|
||||
*/
|
||||
protected boolean beginLookVectorLaunch(boolean resetInterpolationState) {
|
||||
setCharging(false);
|
||||
boolean positioned = setLookVectorLaunchPosition(true);
|
||||
syncStartPositionToCurrent();
|
||||
if (resetInterpolationState) {
|
||||
syncPositionStateToCurrent(true);
|
||||
}
|
||||
return positioned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared launch transition with explicit owner/look vector data.
|
||||
*/
|
||||
protected boolean beginLookVectorLaunch(EntityLivingBase owner, Vec3 look, boolean resetInterpolationState) {
|
||||
setCharging(false);
|
||||
boolean positioned = setLookVectorLaunchPosition(owner, look, true);
|
||||
syncStartPositionToCurrent();
|
||||
if (resetInterpolationState) {
|
||||
syncPositionStateToCurrent(true);
|
||||
}
|
||||
return positioned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the owner's look vector.
|
||||
* Falls back to yaw/pitch math if look vector is unavailable.
|
||||
*/
|
||||
protected Vec3 getOwnerLookVector() {
|
||||
Entity owner = getOwnerEntity();
|
||||
if (!(owner instanceof EntityPlayer)) return;
|
||||
if (!(owner instanceof EntityLivingBase)) return null;
|
||||
|
||||
Vec3 look = owner.getLookVec();
|
||||
double eyeY = owner.posY + owner.getEyeHeight();
|
||||
float frontDist = Math.max(0.5f, size * 0.5f);
|
||||
if (look != null) return look;
|
||||
|
||||
double newX = owner.posX + look.xCoord * frontDist;
|
||||
double newY = eyeY + look.yCoord * frontDist;
|
||||
double newZ = owner.posZ + look.zCoord * frontDist;
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
return Vec3.createVectorHelper(
|
||||
-Math.sin(yaw) * Math.cos(pitch),
|
||||
-Math.sin(pitch),
|
||||
Math.cos(yaw) * Math.cos(pitch)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared launch origin: owner position + chest height + look-vector offset.
|
||||
*
|
||||
* @param syncPositionState When true, sync prev/lastTick/interp targets to this position.
|
||||
*/
|
||||
protected boolean setLookVectorLaunchPosition(boolean syncPositionState) {
|
||||
Entity owner = getOwnerEntity();
|
||||
if (!(owner instanceof EntityLivingBase)) return false;
|
||||
Vec3 look = getOwnerLookVector();
|
||||
if (look == null) return false;
|
||||
return setLookVectorLaunchPosition((EntityLivingBase) owner, look, syncPositionState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared launch origin using provided owner/look data.
|
||||
*/
|
||||
protected boolean setLookVectorLaunchPosition(EntityLivingBase owner, Vec3 look, boolean syncPositionState) {
|
||||
if (owner == null || look == null) return false;
|
||||
|
||||
double originX = owner.posX;
|
||||
double originY = owner.posY + owner.height * 0.7;
|
||||
double originZ = owner.posZ;
|
||||
|
||||
float frontDist = computeLaunchFrontDistance(owner, look, originX, originY, originZ);
|
||||
double newX = originX + look.xCoord * frontDist;
|
||||
double newY = originY + look.yCoord * frontDist;
|
||||
double newZ = originZ + look.zCoord * frontDist;
|
||||
|
||||
setPosition(newX, newY, newZ);
|
||||
prevPosX = newX;
|
||||
prevPosY = newY;
|
||||
prevPosZ = newZ;
|
||||
if (syncPositionState) {
|
||||
syncPositionState(newX, newY, newZ, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Radius used to clear the caster hitbox on launch.
|
||||
* Subclasses may override if their effective launch footprint differs from {@code size}.
|
||||
*/
|
||||
protected float getLaunchClearanceRadius() {
|
||||
return Math.max(0.1f, size * 0.5f);
|
||||
}
|
||||
|
||||
private float computeLaunchFrontDistance(EntityLivingBase owner, Vec3 look, double originX, double originY, double originZ) {
|
||||
// Legacy baseline offset retained as lower bound.
|
||||
float baseline = size * 0.4f;
|
||||
float clearance = getLaunchClearanceRadius() + 0.05f;
|
||||
|
||||
AxisAlignedBB bb = owner.boundingBox;
|
||||
if (bb == null) {
|
||||
return Math.max(baseline, owner.width * 0.5f + clearance);
|
||||
}
|
||||
|
||||
double dx = look.xCoord;
|
||||
double dy = look.yCoord;
|
||||
double dz = look.zCoord;
|
||||
|
||||
double tx = computeRayExitT(originX, dx, bb.minX, bb.maxX);
|
||||
double ty = computeRayExitT(originY, dy, bb.minY, bb.maxY);
|
||||
double tz = computeRayExitT(originZ, dz, bb.minZ, bb.maxZ);
|
||||
|
||||
double tExit = Math.min(tx, Math.min(ty, tz));
|
||||
if (Double.isInfinite(tExit) || tExit < 0) {
|
||||
return Math.max(baseline, owner.width * 0.5f + clearance);
|
||||
}
|
||||
|
||||
return (float) Math.max(baseline, tExit + clearance);
|
||||
}
|
||||
|
||||
private double computeRayExitT(double origin, double dir, double min, double max) {
|
||||
final double EPS = 1.0e-6;
|
||||
if (dir > EPS) {
|
||||
return (max - origin) / dir;
|
||||
} else if (dir < -EPS) {
|
||||
return (min - origin) / dir;
|
||||
}
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set projectile motion along owner look vector.
|
||||
*
|
||||
* @return true if owner/look were available.
|
||||
*/
|
||||
protected boolean setMotionAlongLookVector(float speed) {
|
||||
Vec3 look = getOwnerLookVector();
|
||||
if (look == null) return false;
|
||||
|
||||
motionX = look.xCoord * speed;
|
||||
motionY = look.yCoord * speed;
|
||||
motionZ = look.zCoord * speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set motion along owner look vector or apply explicit fallback values.
|
||||
*/
|
||||
protected void setMotionAlongLookVectorOrFallback(float speed, double fallbackX, double fallbackY, double fallbackZ) {
|
||||
if (!setMotionAlongLookVector(speed)) {
|
||||
motionX = fallbackX;
|
||||
motionY = fallbackY;
|
||||
motionZ = fallbackZ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set projectile motion toward a target from a given source point.
|
||||
*
|
||||
* @return true if target was valid and motion was updated.
|
||||
*/
|
||||
protected boolean setMotionTowardTarget(EntityLivingBase target, double sourceX, double sourceY, double sourceZ, float speed) {
|
||||
if (target == null) return false;
|
||||
|
||||
double dx = target.posX - sourceX;
|
||||
double dy = (target.posY + target.getEyeHeight()) - sourceY;
|
||||
double dz = target.posZ - sourceZ;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len <= 0.0001) return false;
|
||||
|
||||
motionX = (dx / len) * speed;
|
||||
motionY = (dy / len) * speed;
|
||||
motionZ = (dz / len) * speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set motion toward target from source point; when target is unavailable,
|
||||
* fall back to owner look vector, then explicit fallback values.
|
||||
*/
|
||||
protected void setMotionTowardTargetOrLookVector(EntityLivingBase target, double sourceX, double sourceY, double sourceZ, float speed,
|
||||
double fallbackX, double fallbackY, double fallbackZ) {
|
||||
if (!setMotionTowardTarget(target, sourceX, sourceY, sourceZ, speed)) {
|
||||
setMotionAlongLookVectorOrFallback(speed, fallbackX, fallbackY, fallbackZ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync start position (distance origin) to the current entity position.
|
||||
*/
|
||||
protected void syncStartPositionToCurrent() {
|
||||
startX = posX;
|
||||
startY = posY;
|
||||
startZ = posZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync prev/lastTick and optional interpolation state to an explicit position.
|
||||
*/
|
||||
protected void syncPositionState(double x, double y, double z, boolean resetInterpolation) {
|
||||
prevPosX = x;
|
||||
prevPosY = y;
|
||||
prevPosZ = z;
|
||||
lastTickPosX = x;
|
||||
lastTickPosY = y;
|
||||
lastTickPosZ = z;
|
||||
|
||||
if (resetInterpolation) {
|
||||
interpTargetX = x;
|
||||
interpTargetY = y;
|
||||
interpTargetZ = z;
|
||||
interpTargetMotionX = motionX;
|
||||
interpTargetMotionY = motionY;
|
||||
interpTargetMotionZ = motionZ;
|
||||
interpSteps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync prev/lastTick and optional interpolation state to current entity position.
|
||||
*/
|
||||
protected void syncPositionStateToCurrent(boolean resetInterpolation) {
|
||||
syncPositionState(posX, posY, posZ, resetInterpolation);
|
||||
}
|
||||
|
||||
// ==================== CHARGING METHODS (projectile-specific) ====================
|
||||
@@ -1118,6 +1328,23 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
this.motionY = nbt.getDouble("MotionY");
|
||||
this.motionZ = nbt.getDouble("MotionZ");
|
||||
|
||||
// Authoritative launch origin comes from spawn NBT (set during fire/startMoving).
|
||||
// Sync full client position state to this origin to avoid first-tick spawn/interp pops.
|
||||
this.setPosition(this.startX, this.startY, this.startZ);
|
||||
this.prevPosX = this.startX;
|
||||
this.prevPosY = this.startY;
|
||||
this.prevPosZ = this.startZ;
|
||||
this.lastTickPosX = this.startX;
|
||||
this.lastTickPosY = this.startY;
|
||||
this.lastTickPosZ = this.startZ;
|
||||
this.interpTargetX = this.startX;
|
||||
this.interpTargetY = this.startY;
|
||||
this.interpTargetZ = this.startZ;
|
||||
this.interpTargetMotionX = this.motionX;
|
||||
this.interpTargetMotionY = this.motionY;
|
||||
this.interpTargetMotionZ = this.motionZ;
|
||||
this.interpSteps = 0;
|
||||
|
||||
// Always initialize render size from NBT size so entities
|
||||
// loading in from outside render distance render correctly
|
||||
this.renderCurrentSize = this.size;
|
||||
|
||||
@@ -102,55 +102,16 @@ public class EntityEnergySlicer extends EntityEnergyProjectile {
|
||||
* Start preview firing.
|
||||
*/
|
||||
public void startPreviewFiring() {
|
||||
setCharging(false);
|
||||
startX = posX;
|
||||
startY = posY;
|
||||
startZ = posZ;
|
||||
prevPosX = posX;
|
||||
prevPosY = posY;
|
||||
prevPosZ = posZ;
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
if (owner != null) {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
motionX = -Math.sin(yaw) * getSpeed();
|
||||
motionY = 0;
|
||||
motionZ = Math.cos(yaw) * getSpeed();
|
||||
} else {
|
||||
motionX = getSpeed();
|
||||
motionY = 0;
|
||||
motionZ = 0;
|
||||
}
|
||||
beginLookVectorLaunch(true);
|
||||
setMotionAlongLookVectorOrFallback(getSpeed(), getSpeed(), 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the slicer moving (exit charging mode).
|
||||
*/
|
||||
public void startMoving(EntityLivingBase target) {
|
||||
setCharging(false);
|
||||
snapToPlayerLookVector();
|
||||
startX = posX;
|
||||
startY = posY;
|
||||
startZ = posZ;
|
||||
|
||||
Entity owner = getOwnerEntity();
|
||||
if (target != null) {
|
||||
double dx = target.posX - posX;
|
||||
double dy = (target.posY + target.getEyeHeight()) - posY;
|
||||
double dz = target.posZ - posZ;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0) {
|
||||
motionX = (dx / len) * getSpeed();
|
||||
motionY = (dy / len) * getSpeed();
|
||||
motionZ = (dz / len) * getSpeed();
|
||||
}
|
||||
} else if (owner != null) {
|
||||
float yaw = (float) Math.toRadians(owner.rotationYaw);
|
||||
float pitch = (float) Math.toRadians(owner.rotationPitch);
|
||||
motionX = -Math.sin(yaw) * Math.cos(pitch) * getSpeed();
|
||||
motionY = -Math.sin(pitch) * getSpeed();
|
||||
motionZ = Math.cos(yaw) * Math.cos(pitch) * getSpeed();
|
||||
}
|
||||
beginLookVectorLaunch(false);
|
||||
setMotionTowardTargetOrLookVector(target, posX, posY, posZ, getSpeed(), getSpeed(), 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,6 +36,8 @@ import kamkeel.npcs.network.packets.data.npc.WeaponNpcPacket;
|
||||
import kamkeel.npcs.network.packets.data.script.ScriptOverlayClosePacket;
|
||||
import kamkeel.npcs.network.packets.data.script.ScriptOverlayDataPacket;
|
||||
import kamkeel.npcs.network.packets.data.script.ScriptedParticlePacket;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeRemovePacket;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
|
||||
import kamkeel.npcs.network.packets.player.AuctionActionPacket;
|
||||
@@ -585,6 +587,8 @@ public class PacketHandler {
|
||||
// Data | Ability Packets
|
||||
DATA_PACKET.registerPacket(new TelegraphSpawnPacket());
|
||||
DATA_PACKET.registerPacket(new TelegraphRemovePacket());
|
||||
DATA_PACKET.registerPacket(new EnergyChargeSpawnPacket());
|
||||
DATA_PACKET.registerPacket(new EnergyChargeRemovePacket());
|
||||
DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket());
|
||||
DATA_PACKET.registerPacket(new PlayerAbilityStatePacket());
|
||||
DATA_PACKET.registerPacket(new AbilityHotbarSyncPacket());
|
||||
|
||||
@@ -70,4 +70,8 @@ public enum EnumDataPacket {
|
||||
|
||||
// Auction System
|
||||
AUCTION_DATA,
|
||||
|
||||
// Energy Charge Preview
|
||||
ENERGY_CHARGE_SPAWN,
|
||||
ENERGY_CHARGE_REMOVE,
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package kamkeel.npcs.network.packets.data.energycharge;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.enums.EnumDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Removes a client-only charging preview entity.
|
||||
*/
|
||||
public final class EnergyChargeRemovePacket extends AbstractPacket {
|
||||
public static final String packetName = "Data|EnergyChargeRemove";
|
||||
|
||||
private String instanceId;
|
||||
|
||||
public EnergyChargeRemovePacket() {
|
||||
}
|
||||
|
||||
public EnergyChargeRemovePacket(String instanceId) {
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumDataPacket.ENERGY_CHARGE_REMOVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.DATA_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
ByteBufUtils.writeString(out, instanceId);
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
String id = ByteBufUtils.readString(in);
|
||||
if (id != null && EnergyChargePreviewManager.ClientInstance != null) {
|
||||
EnergyChargePreviewManager.ClientInstance.removePreview(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendToTracking(String instanceId, Entity trackingEntity) {
|
||||
if (instanceId == null || trackingEntity == null) return;
|
||||
PacketHandler.Instance.sendTracking(new EnergyChargeRemovePacket(instanceId), trackingEntity);
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package kamkeel.npcs.network.packets.data.energycharge;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.enums.EnumDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
* Spawns a client-only charging preview entity from serialized spawn data.
|
||||
*/
|
||||
public final class EnergyChargeSpawnPacket extends AbstractPacket {
|
||||
public static final String packetName = "Data|EnergyChargeSpawn";
|
||||
|
||||
private String instanceId;
|
||||
private String entityClassName;
|
||||
private NBTTagCompound spawnNbt;
|
||||
|
||||
public EnergyChargeSpawnPacket() {
|
||||
}
|
||||
|
||||
public EnergyChargeSpawnPacket(String instanceId, String entityClassName, NBTTagCompound spawnNbt) {
|
||||
this.instanceId = instanceId;
|
||||
this.entityClassName = entityClassName;
|
||||
this.spawnNbt = spawnNbt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumDataPacket.ENERGY_CHARGE_SPAWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.DATA_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
ByteBufUtils.writeString(out, instanceId);
|
||||
ByteBufUtils.writeString(out, entityClassName);
|
||||
ByteBufUtils.writeNBT(out, spawnNbt);
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
String id = ByteBufUtils.readString(in);
|
||||
String className = ByteBufUtils.readString(in);
|
||||
NBTTagCompound nbt = ByteBufUtils.readNBT(in);
|
||||
|
||||
if (id == null || className == null || nbt == null) return;
|
||||
if (EnergyChargePreviewManager.ClientInstance == null) return;
|
||||
|
||||
World world = Minecraft.getMinecraft().theWorld;
|
||||
if (world == null) return;
|
||||
|
||||
EntityEnergyProjectile preview = createPreviewEntity(className, world);
|
||||
if (preview == null) return;
|
||||
|
||||
preview.setPreviewMode(true);
|
||||
preview.importSpawnNBT(nbt);
|
||||
|
||||
// Ensure the preview follows current owner immediately on first render frame.
|
||||
Entity owner = preview.getOwnerEntity();
|
||||
if (owner instanceof net.minecraft.entity.EntityLivingBase) {
|
||||
preview.setPreviewOwner((net.minecraft.entity.EntityLivingBase) owner);
|
||||
}
|
||||
|
||||
EnergyChargePreviewManager.ClientInstance.addPreview(id, preview);
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
private static EntityEnergyProjectile createPreviewEntity(String className, World world) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
if (!EntityEnergyProjectile.class.isAssignableFrom(clazz)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Constructor<?> constructor = clazz.getConstructor(World.class);
|
||||
Object instance = constructor.newInstance(world);
|
||||
return (EntityEnergyProjectile) instance;
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendToTracking(String instanceId, EntityEnergyProjectile previewEntity, Entity trackingEntity) {
|
||||
if (previewEntity == null || trackingEntity == null) return;
|
||||
String className = previewEntity.getClass().getName();
|
||||
NBTTagCompound nbt = previewEntity.exportSpawnNBT();
|
||||
PacketHandler.Instance.sendTracking(new EnergyChargeSpawnPacket(instanceId, className, nbt), trackingEntity);
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,11 @@ import kamkeel.npcs.client.renderer.RenderAbilitySweeper;
|
||||
import kamkeel.npcs.client.renderer.RenderAbilityZone;
|
||||
import kamkeel.npcs.client.renderer.RenderAbilityDome;
|
||||
import kamkeel.npcs.client.renderer.RenderAbilityPanel;
|
||||
import kamkeel.npcs.client.renderer.EnergyChargePreviewRenderer;
|
||||
import kamkeel.npcs.client.renderer.RenderEnergySlicer;
|
||||
import kamkeel.npcs.client.renderer.TelegraphRenderer;
|
||||
import kamkeel.npcs.client.renderer.lightning.LightningHandler;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphManager;
|
||||
import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import kamkeel.npcs.entity.EntityAbilityDisc;
|
||||
@@ -356,6 +358,10 @@ public class ClientProxy extends CommonProxy {
|
||||
TelegraphManager.initClient();
|
||||
MinecraftForge.EVENT_BUS.register(new TelegraphRenderer());
|
||||
|
||||
// Energy charge preview rendering system
|
||||
EnergyChargePreviewManager.initClient();
|
||||
MinecraftForge.EVENT_BUS.register(new EnergyChargePreviewRenderer());
|
||||
|
||||
// Lightning effect rendering system
|
||||
MinecraftForge.EVENT_BUS.register(new LightningHandler());
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import cpw.mods.fml.common.gameevent.TickEvent.Phase;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import kamkeel.npcs.client.renderer.lightning.LightningBolt;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargePreviewManager;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphManager;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.packets.data.RequestProperSpawnData;
|
||||
@@ -62,6 +63,9 @@ public class ClientTickHandler {
|
||||
ClientCacheHandler.clearCache();
|
||||
ClientAbilityState.reset();
|
||||
RequestProperSpawnData.clear();
|
||||
if (EnergyChargePreviewManager.ClientInstance != null) {
|
||||
EnergyChargePreviewManager.ClientInstance.clear();
|
||||
}
|
||||
}
|
||||
this.prevWorld = mc.theWorld;
|
||||
}
|
||||
@@ -164,6 +168,11 @@ public class ClientTickHandler {
|
||||
TelegraphManager.ClientInstance.tick(mc.theWorld);
|
||||
}
|
||||
|
||||
// Tick packet-driven energy charge previews
|
||||
if (EnergyChargePreviewManager.ClientInstance != null) {
|
||||
EnergyChargePreviewManager.ClientInstance.tick(mc.theWorld);
|
||||
}
|
||||
|
||||
// Update lightning bolts for ability effects
|
||||
LightningBolt.updateAll();
|
||||
|
||||
|
||||
@@ -1834,12 +1834,15 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti
|
||||
try {
|
||||
readSpawnData(ByteBufUtils.readNBT(buf));
|
||||
} catch (IOException e) {
|
||||
RequestProperSpawnData.reportMissingData(this);
|
||||
if(this.worldObj != null && this.worldObj.isRemote){
|
||||
RequestProperSpawnData.reportMissingData(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readSpawnData(NBTTagCompound compound) {
|
||||
if ((compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) {
|
||||
if (this.worldObj != null && this.worldObj.isRemote &&
|
||||
(compound.hasNoTags() || !compound.hasKey("MaxHealth", Constants.NBT.TAG_DOUBLE))) {
|
||||
RequestProperSpawnData.reportMissingData(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user