Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java
T

696 lines
24 KiB
Java
Raw Normal View History

2026-01-25 20:39:01 -05:00
package kamkeel.npcs.entity;
import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import noppes.npcs.NpcDamageSource;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.List;
/**
* Base class for all ability projectiles (Orb, Disc, Beam, Laser).
* Provides common functionality for visuals, combat, effects, and interpolation.
*
* Design inspired by LouisXIV's energy attack system.
*/
public abstract class EntityAbilityProjectile extends Entity implements IEntityAdditionalSpawnData {
// ==================== VISUAL PROPERTIES ====================
protected float size = 1.0f;
protected int innerColor = 0xFFFFFF;
protected int outerColor = 0x8888FF;
2026-01-26 04:42:23 -05:00
protected boolean outerColorEnabled = true;
protected float outerColorWidth = 0.4f; // Additive offset from inner size
2026-01-28 20:49:16 -03:00
protected float outerColorAlpha = 0.5f;
2026-01-25 20:39:01 -05:00
protected float rotationSpeed = 4.0f;
// ==================== COMBAT PROPERTIES ====================
protected float damage = 7.0f;
protected float knockback = 1.0f;
protected float knockbackUp = 0.1f;
// ==================== EFFECT PROPERTIES ====================
protected int stunDuration = 0;
protected int slowDuration = 0;
protected int slowLevel = 0;
// ==================== EXPLOSION PROPERTIES ====================
protected boolean explosive = false;
protected float explosionRadius = 3.0f;
protected float explosionDamageFalloff = 0.5f;
// ==================== LIGHTNING EFFECT PROPERTIES ====================
protected boolean lightningEffect = false;
protected float lightningDensity = 0.15f; // Bolts spawned per tick (0.15 = ~15% chance per frame)
protected float lightningRadius = 0.5f; // Max distance lightning can arc from center
protected int lightningFadeTime = 6; // Ticks before lightning fades out
// Client-side lightning state (not saved to NBT)
@cpw.mods.fml.relauncher.SideOnly(cpw.mods.fml.relauncher.Side.CLIENT)
public transient Object lightningState; // Actually AttachedLightningRenderer.LightningState
2026-01-25 20:39:01 -05:00
// ==================== LIFESPAN ====================
protected float maxDistance = 30.0f;
protected int maxLifetime = 200;
protected long deathWorldTime = -1; // World time when entity should die (-1 = not set)
// ==================== TRACKING ====================
protected double startX, startY, startZ;
protected int ownerEntityId = -1;
protected int targetEntityId = -1;
// ==================== STATE ====================
protected boolean hasHit = false;
2026-01-28 20:43:32 -05:00
protected boolean previewMode = false; // Client-side preview mode (no damage/effects)
protected EntityNPCInterface previewOwner = null; // Direct reference for GUI preview (no world lookup)
2026-01-25 20:39:01 -05:00
// ==================== ROTATION INTERPOLATION ====================
public float prevRotationValX, prevRotationValY, prevRotationValZ;
public float rotationValX, rotationValY, rotationValZ;
// ==================== SIZE INTERPOLATION ====================
protected float renderCurrentSize;
protected float prevRenderSize;
// ==================== POSITION INTERPOLATION ====================
protected double interpTargetX, interpTargetY, interpTargetZ;
protected double interpTargetMotionX, interpTargetMotionY, interpTargetMotionZ;
protected int interpSteps;
public EntityAbilityProjectile(World world) {
super(world);
this.setSize(0.5f, 0.5f);
this.noClip = false;
this.isImmuneToFire = true;
this.ignoreFrustumCheck = true;
}
/**
* Initialize common properties. Call from subclass constructors.
*/
protected void initProjectile(EntityNPCInterface owner, EntityLivingBase target,
double x, double y, double z,
2026-01-26 04:42:23 -05:00
float size, int innerColor, int outerColor,
2026-01-28 20:49:16 -03:00
boolean outerColorEnabled, float outerColorWidth, float outerColorAlpha, float rotationSpeed,
2026-01-25 20:39:01 -05:00
float damage, float knockback, float knockbackUp,
boolean explosive, float explosionRadius, float explosionDamageFalloff,
int stunDuration, int slowDuration, int slowLevel,
float maxDistance, int maxLifetime) {
initProjectile(owner, target, x, y, z,
2026-01-28 20:49:16 -03:00
size, innerColor, outerColor, outerColorEnabled, outerColorWidth, outerColorAlpha, rotationSpeed,
damage, knockback, knockbackUp,
explosive, explosionRadius, explosionDamageFalloff,
stunDuration, slowDuration, slowLevel,
maxDistance, maxLifetime,
false, 0.15f, 0.5f, 6); // Default: no lightning, low density, small radius, 6 tick fade
}
/**
* Initialize common properties with lightning effect support.
*/
protected void initProjectile(EntityNPCInterface owner, EntityLivingBase target,
double x, double y, double z,
float size, int innerColor, int outerColor,
2026-01-28 20:49:16 -03:00
boolean outerColorEnabled, float outerColorWidth, float outerColorAlpha, float rotationSpeed,
float damage, float knockback, float knockbackUp,
boolean explosive, float explosionRadius, float explosionDamageFalloff,
int stunDuration, int slowDuration, int slowLevel,
float maxDistance, int maxLifetime,
boolean lightningEffect, float lightningDensity, float lightningRadius, int lightningFadeTime) {
2026-01-25 20:39:01 -05:00
this.setPosition(x, y, z);
this.startX = x;
this.startY = y;
this.startZ = z;
this.ownerEntityId = owner.getEntityId();
this.targetEntityId = target != null ? target.getEntityId() : -1;
// Visual
this.size = size;
this.innerColor = innerColor;
this.outerColor = outerColor;
2026-01-26 04:42:23 -05:00
this.outerColorEnabled = outerColorEnabled;
this.outerColorWidth = outerColorWidth;
2026-01-28 20:49:16 -03:00
this.outerColorAlpha = outerColorAlpha;
2026-01-25 20:39:01 -05:00
this.rotationSpeed = rotationSpeed;
// Combat
this.damage = damage;
this.knockback = knockback;
this.knockbackUp = knockbackUp;
// Explosion
this.explosive = explosive;
this.explosionRadius = explosionRadius;
this.explosionDamageFalloff = explosionDamageFalloff;
// Effects
this.stunDuration = stunDuration;
this.slowDuration = slowDuration;
this.slowLevel = slowLevel;
// Lifespan
this.maxDistance = maxDistance;
this.maxLifetime = maxLifetime;
// deathWorldTime will be set on first tick when world is available
// Lightning
this.lightningEffect = lightningEffect;
this.lightningDensity = lightningDensity;
this.lightningRadius = lightningRadius;
this.lightningFadeTime = lightningFadeTime;
2026-01-25 20:39:01 -05:00
// Initialize render size
this.renderCurrentSize = size;
this.prevRenderSize = size;
}
@Override
protected void entityInit() {
// No DataWatcher needed - using IEntityAdditionalSpawnData with NBT
}
@Override
@SideOnly(Side.CLIENT)
public boolean isInRangeToRenderDist(double distance) {
double d1 = this.boundingBox.getAverageEdgeLength() * 4.0D;
d1 *= 128.0D;
return distance < d1 * d1;
}
@Override
public boolean shouldRenderInPass(int pass) {
return pass == 1; // Translucent pass
}
@Override
public void onUpdate() {
// Store previous values for interpolation
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
this.prevRotationValX = this.rotationValX;
this.prevRotationValY = this.rotationValY;
this.prevRotationValZ = this.rotationValZ;
this.prevRenderSize = this.renderCurrentSize;
2026-01-28 20:43:32 -05:00
// Skip super.onUpdate() in preview mode to avoid world checks
if (!previewMode) {
super.onUpdate();
}
2026-01-25 20:39:01 -05:00
// Update rotation
updateRotation();
// Lerp render size toward actual size
this.renderCurrentSize = this.renderCurrentSize + (this.size - this.renderCurrentSize) * 0.15f;
2026-01-28 20:43:32 -05:00
// Skip lifetime/distance checks in preview mode
if (!previewMode) {
// Set death time on first tick if not already set (handles chunk load/unload)
if (deathWorldTime < 0 && worldObj != null) {
deathWorldTime = worldObj.getTotalWorldTime() + maxLifetime;
}
2026-01-25 20:39:01 -05:00
2026-01-28 20:43:32 -05:00
// Check lifespan using world time (survives chunk unload/reload)
if (deathWorldTime > 0 && worldObj.getTotalWorldTime() >= deathWorldTime) {
this.setDead();
return;
}
2026-01-25 20:39:01 -05:00
2026-01-28 20:43:32 -05:00
// Check max distance (subclass can override if needed)
if (checkMaxDistance()) {
this.setDead();
return;
}
2026-01-25 20:39:01 -05:00
2026-01-28 20:43:32 -05:00
if (hasHit) {
this.setDead();
return;
}
2026-01-25 20:39:01 -05:00
}
// Subclass-specific update
updateProjectile();
}
/**
* Update rotation values. Override for custom rotation behavior.
*/
protected void updateRotation() {
this.rotationValX += rotationSpeed * 0.7f;
this.rotationValY += rotationSpeed;
this.rotationValZ += rotationSpeed * 0.5f;
if (this.rotationValX > 360.0f) this.rotationValX -= 360.0f;
if (this.rotationValY > 360.0f) this.rotationValY -= 360.0f;
if (this.rotationValZ > 360.0f) this.rotationValZ -= 360.0f;
}
/**
* Check if projectile has exceeded max distance.
* @return true if should die
*/
protected boolean checkMaxDistance() {
double distTraveled = Math.sqrt(
(posX - startX) * (posX - startX) +
(posY - startY) * (posY - startY) +
(posZ - startZ) * (posZ - startZ)
);
return distTraveled >= maxDistance;
}
/**
* Subclass-specific update logic. Called every tick.
*/
protected abstract void updateProjectile();
// ==================== POSITION INTERPOLATION ====================
@Override
public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int posRotationIncrements) {
this.interpTargetX = x;
this.interpTargetY = y;
this.interpTargetZ = z;
this.interpSteps = posRotationIncrements;
}
@Override
public void setVelocity(double motionX, double motionY, double motionZ) {
this.interpTargetMotionX = motionX;
this.interpTargetMotionY = motionY;
this.interpTargetMotionZ = motionZ;
this.motionX = motionX;
this.motionY = motionY;
this.motionZ = motionZ;
}
/**
* Handle client-side position interpolation.
*/
protected void handleClientInterpolation() {
if (this.interpSteps > 0) {
double newX = this.posX + (this.interpTargetX - this.posX) / this.interpSteps;
double newY = this.posY + (this.interpTargetY - this.posY) / this.interpSteps;
double newZ = this.posZ + (this.interpTargetZ - this.posZ) / this.interpSteps;
this.motionX = this.motionX + (this.interpTargetMotionX - this.motionX) / this.interpSteps;
this.motionY = this.motionY + (this.interpTargetMotionY - this.motionY) / this.interpSteps;
this.motionZ = this.motionZ + (this.interpTargetMotionZ - this.motionZ) / this.interpSteps;
this.setPosition(newX, newY, newZ);
this.interpSteps--;
} else {
this.posX += this.motionX;
this.posY += this.motionY;
this.posZ += this.motionZ;
this.setPosition(this.posX, this.posY, this.posZ);
}
}
2026-01-28 20:43:32 -05:00
// ==================== PREVIEW MODE ====================
/**
* Set preview mode for GUI display.
* In preview mode, no damage or world effects are applied.
*/
public void setPreviewMode(boolean preview) {
this.previewMode = preview;
}
/**
* Check if entity is in preview mode.
*/
public boolean isPreviewMode() {
return previewMode;
}
2026-01-25 20:39:01 -05:00
// ==================== DAMAGE & EFFECTS ====================
protected void applyDamage(EntityLivingBase target) {
2026-01-28 20:43:32 -05:00
if (previewMode) return; // Skip damage in preview mode
2026-01-25 20:39:01 -05:00
applyDamage(target, this.damage, this.knockback);
}
protected void applyDamage(EntityLivingBase target, float dmg, float kb) {
2026-01-28 20:43:32 -05:00
if (previewMode) return; // Skip damage in preview mode
2026-01-25 20:39:01 -05:00
Entity owner = getOwner();
EntityNPCInterface npc = (owner instanceof EntityNPCInterface) ? (EntityNPCInterface) owner : null;
if (npc != null) {
target.attackEntityFrom(new NpcDamageSource("npc_ability", npc), dmg);
} else {
target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg);
}
if (kb > 0) {
double dx = target.posX - posX;
double dz = target.posZ - posZ;
double len = Math.sqrt(dx * dx + dz * dz);
if (len > 0) {
double kbX = (dx / len) * kb * 0.5;
double kbZ = (dz / len) * kb * 0.5;
double kbY = knockbackUp > 0 ? knockbackUp : 0.1;
target.addVelocity(kbX, kbY, kbZ);
target.velocityChanged = true;
}
}
applyEffects(target);
}
protected void applyEffects(EntityLivingBase target) {
if (stunDuration > 0) {
target.addPotionEffect(new PotionEffect(Potion.moveSlowdown.id, stunDuration, 10));
target.addPotionEffect(new PotionEffect(Potion.weakness.id, stunDuration, 2));
}
if (slowDuration > 0 && slowLevel > 0) {
target.addPotionEffect(new PotionEffect(Potion.moveSlowdown.id, slowDuration, slowLevel));
}
}
protected void doExplosion() {
2026-01-28 20:43:32 -05:00
if (previewMode) return; // Skip explosion in preview mode
2026-01-25 20:39:01 -05:00
worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f);
Entity owner = getOwner();
AxisAlignedBB explosionBox = AxisAlignedBB.getBoundingBox(
posX - explosionRadius, posY - explosionRadius, posZ - explosionRadius,
posX + explosionRadius, posY + explosionRadius, posZ + explosionRadius
);
@SuppressWarnings("unchecked")
List<EntityLivingBase> targets = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, explosionBox);
for (EntityLivingBase target : targets) {
if (target == owner) continue;
double dist = Math.sqrt(
Math.pow(target.posX - posX, 2) +
Math.pow(target.posY - posY, 2) +
Math.pow(target.posZ - posZ, 2)
);
if (dist <= explosionRadius) {
float falloff = 1.0f - (float) (dist / explosionRadius) * explosionDamageFalloff;
applyDamage(target, damage * falloff, knockback * falloff);
}
}
}
// ==================== ENTITY HELPERS ====================
protected Entity getOwner() {
2026-01-28 20:43:32 -05:00
// In preview mode, use direct reference (no world lookup)
if (previewMode && previewOwner != null) {
return previewOwner;
}
2026-01-25 20:39:01 -05:00
if (ownerEntityId == -1) return null;
return worldObj.getEntityByID(ownerEntityId);
}
2026-01-28 20:43:32 -05:00
/**
* Set the preview owner for GUI preview mode.
* This allows anchor point calculations without world entity lookup.
*/
public void setPreviewOwner(EntityNPCInterface owner) {
this.previewOwner = owner;
}
2026-01-25 20:39:01 -05:00
protected Entity getTarget() {
if (targetEntityId == -1) return null;
return worldObj.getEntityByID(targetEntityId);
}
/**
* Check if an entity should be ignored for collision.
*/
protected boolean shouldIgnoreEntity(Entity entity) {
Entity owner = getOwner();
if (entity == owner) return true;
if (owner instanceof EntityNPCInterface && entity instanceof EntityNPCInterface) {
EntityNPCInterface ownerNpc = (EntityNPCInterface) owner;
EntityNPCInterface targetNpc = (EntityNPCInterface) entity;
if (ownerNpc.faction.id == targetNpc.faction.id) return true;
}
return false;
}
// ==================== VISUAL GETTERS ====================
public float getSize() {
return size;
}
public int getInnerColor() {
return innerColor;
}
public int getOuterColor() {
return outerColor;
}
2026-01-26 04:42:23 -05:00
public boolean isOuterColorEnabled() {
return outerColorEnabled;
}
public float getOuterColorWidth() {
return outerColorWidth;
}
2026-01-28 20:49:16 -03:00
public float getOuterColorAlpha() {
return outerColorAlpha;
}
2026-01-25 20:39:01 -05:00
public float getRotationSpeed() {
return rotationSpeed;
}
public float getInterpolatedRotationX(float partialTicks) {
return this.prevRotationValX + (this.rotationValX - this.prevRotationValX) * partialTicks;
}
public float getInterpolatedRotationY(float partialTicks) {
return this.prevRotationValY + (this.rotationValY - this.prevRotationValY) * partialTicks;
}
public float getInterpolatedRotationZ(float partialTicks) {
return this.prevRotationValZ + (this.rotationValZ - this.prevRotationValZ) * partialTicks;
}
public float getInterpolatedSize(float partialTicks) {
return this.prevRenderSize + (this.renderCurrentSize - this.prevRenderSize) * partialTicks;
}
// ==================== LIGHTNING GETTERS ====================
public boolean hasLightningEffect() {
return lightningEffect;
}
public float getLightningDensity() {
return lightningDensity;
}
public float getLightningRadius() {
return lightningRadius;
}
public int getLightningFadeTime() {
return lightningFadeTime;
}
2026-01-25 20:39:01 -05:00
// ==================== BRIGHTNESS ====================
@Override
public float getBrightness(float partialTicks) {
return 1.0f;
}
@Override
@SideOnly(Side.CLIENT)
public int getBrightnessForRender(float partialTicks) {
return 0xF000F0;
}
// ==================== NBT ====================
@Override
protected void readEntityFromNBT(NBTTagCompound nbt) {
readBaseNBT(nbt);
readProjectileNBT(nbt);
}
@Override
protected void writeEntityToNBT(NBTTagCompound nbt) {
writeBaseNBT(nbt);
writeProjectileNBT(nbt);
}
/**
* Read base projectile properties from NBT.
*/
protected void readBaseNBT(NBTTagCompound nbt) {
this.size = nbt.getFloat("Size");
this.innerColor = nbt.getInteger("InnerColor");
this.outerColor = nbt.getInteger("OuterColor");
2026-01-26 04:42:23 -05:00
this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled");
2026-01-28 20:49:16 -03:00
this.outerColorAlpha = nbt.hasKey("OuterColorAlpha") ? nbt.getFloat("OuterColorAlpha") : 0.5f;
this.outerColorWidth = nbt.hasKey("OuterColorWidth") ? nbt.getFloat("OuterColorWidth") : 0.4f;
2026-01-25 20:39:01 -05:00
this.rotationSpeed = nbt.hasKey("RotationSpeed") ? nbt.getFloat("RotationSpeed") : 4.0f;
this.damage = nbt.getFloat("Damage");
this.knockback = nbt.getFloat("Knockback");
this.knockbackUp = nbt.getFloat("KnockbackUp");
this.explosive = nbt.getBoolean("Explosive");
this.explosionRadius = nbt.getFloat("ExplosionRadius");
this.explosionDamageFalloff = nbt.getFloat("ExplosionDamageFalloff");
this.stunDuration = nbt.getInteger("StunDuration");
this.slowDuration = nbt.getInteger("SlowDuration");
this.slowLevel = nbt.getInteger("SlowLevel");
this.maxDistance = nbt.getFloat("MaxDistance");
this.maxLifetime = nbt.getInteger("MaxLifetime");
this.deathWorldTime = nbt.hasKey("DeathWorldTime") ? nbt.getLong("DeathWorldTime") : -1;
this.startX = nbt.getDouble("StartX");
this.startY = nbt.getDouble("StartY");
this.startZ = nbt.getDouble("StartZ");
this.ownerEntityId = nbt.getInteger("OwnerId");
this.targetEntityId = nbt.getInteger("TargetId");
this.motionX = nbt.getDouble("MotionX");
this.motionY = nbt.getDouble("MotionY");
this.motionZ = nbt.getDouble("MotionZ");
// Lightning
this.lightningEffect = nbt.getBoolean("LightningEffect");
this.lightningDensity = nbt.hasKey("LightningDensity") ? nbt.getFloat("LightningDensity") : 0.15f;
this.lightningRadius = nbt.hasKey("LightningRadius") ? nbt.getFloat("LightningRadius") : 0.5f;
this.lightningFadeTime = nbt.hasKey("LightningFadeTime") ? nbt.getInteger("LightningFadeTime") : 6;
2026-01-25 20:39:01 -05:00
if (this.worldObj != null && this.worldObj.isRemote) {
this.renderCurrentSize = this.size;
this.prevRenderSize = this.size;
}
}
/**
* Write base projectile properties to NBT.
*/
protected void writeBaseNBT(NBTTagCompound nbt) {
nbt.setFloat("Size", size);
nbt.setInteger("InnerColor", innerColor);
nbt.setInteger("OuterColor", outerColor);
2026-01-26 04:42:23 -05:00
nbt.setBoolean("OuterColorEnabled", outerColorEnabled);
nbt.setFloat("OuterColorWidth", outerColorWidth);
2026-01-28 20:49:16 -03:00
nbt.setFloat("OuterColorAlpha", outerColorAlpha);
2026-01-25 20:39:01 -05:00
nbt.setFloat("RotationSpeed", rotationSpeed);
nbt.setFloat("Damage", damage);
nbt.setFloat("Knockback", knockback);
nbt.setFloat("KnockbackUp", knockbackUp);
nbt.setBoolean("Explosive", explosive);
nbt.setFloat("ExplosionRadius", explosionRadius);
nbt.setFloat("ExplosionDamageFalloff", explosionDamageFalloff);
nbt.setInteger("StunDuration", stunDuration);
nbt.setInteger("SlowDuration", slowDuration);
nbt.setInteger("SlowLevel", slowLevel);
nbt.setFloat("MaxDistance", maxDistance);
nbt.setLong("DeathWorldTime", deathWorldTime);
nbt.setInteger("MaxLifetime", maxLifetime);
nbt.setDouble("StartX", startX);
nbt.setDouble("StartY", startY);
nbt.setDouble("StartZ", startZ);
nbt.setInteger("OwnerId", ownerEntityId);
nbt.setInteger("TargetId", targetEntityId);
nbt.setDouble("MotionX", motionX);
nbt.setDouble("MotionY", motionY);
nbt.setDouble("MotionZ", motionZ);
// Lightning
nbt.setBoolean("LightningEffect", lightningEffect);
nbt.setFloat("LightningDensity", lightningDensity);
nbt.setFloat("LightningRadius", lightningRadius);
nbt.setInteger("LightningFadeTime", lightningFadeTime);
2026-01-25 20:39:01 -05:00
}
/**
* Subclass-specific NBT reading.
*/
protected abstract void readProjectileNBT(NBTTagCompound nbt);
/**
* Subclass-specific NBT writing.
*/
protected abstract void writeProjectileNBT(NBTTagCompound nbt);
// ==================== SPAWN DATA ====================
@Override
public void writeSpawnData(ByteBuf buffer) {
try {
NBTTagCompound compound = new NBTTagCompound();
this.writeEntityToNBT(compound);
cpw.mods.fml.common.network.ByteBufUtils.writeTag(buffer, compound);
} catch (Exception ignored) {}
}
@Override
public void readSpawnData(ByteBuf buffer) {
try {
NBTTagCompound compound = cpw.mods.fml.common.network.ByteBufUtils.readTag(buffer);
if (compound != null) {
this.readEntityFromNBT(compound);
}
} catch (Exception ignored) {}
}
// ==================== COLLISION SETTINGS ====================
@Override
public boolean canBeCollidedWith() {
return false;
}
@Override
public boolean canBePushed() {
return false;
}
@Override
protected boolean canTriggerWalking() {
return false;
}
@Override
public boolean isBurning() {
return false;
}
@SideOnly(Side.CLIENT)
public float getShadowSize() {
return 0.0f;
}
}