Initial commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package electroblob.wizardry.entity;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
public class EntityArc extends Entity implements IEntityAdditionalSpawnData {
|
||||
|
||||
public int textureIndex = 0;
|
||||
public double x1, y1, z1, x2, y2, z2;
|
||||
// The number of ticks the arc lasts for before disappearing
|
||||
public int lifetime = 3;
|
||||
public double offsetX, offsetZ;
|
||||
|
||||
public EntityArc(World par1World) {
|
||||
super(par1World);
|
||||
textureIndex = this.rand.nextInt(16);
|
||||
this.ignoreFrustumCheck = true;
|
||||
}
|
||||
|
||||
public void setEndpointCoords(double x1, double y1, double z1, double x2, double y2, double z2){
|
||||
this.x1 = x1;
|
||||
this.y1 = y1;
|
||||
this.z1 = z1;
|
||||
this.x2 = x2;
|
||||
this.y2 = y2;
|
||||
this.z2 = z2;
|
||||
this.setPosition(x2, y2, z2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
if(this.ticksExisted >= lifetime){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
protected void entityInit()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound) {
|
||||
// Nothing needed here; arc is merely a graphic effect that only exists for a few ticks; as such there is no need to save it.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInRangeToRenderDist(double distance) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data) {
|
||||
data.writeDouble(this.x1);
|
||||
data.writeDouble(this.y1);
|
||||
data.writeDouble(this.z1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data) {
|
||||
this.x1 = data.readDouble();
|
||||
this.y1 = data.readDouble();
|
||||
this.z1 = data.readDouble();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package electroblob.wizardry.entity;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.item.EntityFallingBlock;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class EntityMeteor extends EntityFallingBlock {
|
||||
|
||||
/** The entity blast multiplier. Only some projectiles cause a blast, which is why this isn't in EntityMagicProjectile. */
|
||||
public float blastMultiplier;
|
||||
|
||||
public EntityMeteor(World world){
|
||||
super(world);
|
||||
// Superconstructor doesn't call this.
|
||||
this.setSize(0.98F, 0.98F);
|
||||
}
|
||||
|
||||
public EntityMeteor(World world, double x, double y, double z, float blastMultiplier){
|
||||
super(world, x, y, z, WizardryBlocks.meteor.getDefaultState());
|
||||
this.motionY = -1.0D;
|
||||
this.setFire(200);
|
||||
this.blastMultiplier = blastMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getYOffset() {
|
||||
return this.height / 2.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 16 == 1 && worldObj.isRemote){
|
||||
Wizardry.proxy.playMovingSound(this, WizardrySounds.SPELL_LOOP_FIRE, 3.0f, 1.0f, false);
|
||||
}
|
||||
|
||||
// You'd think the best way to do this would be to call super and do all the exploding stuff in fall() instead.
|
||||
// However, for some reason, fallTile is null on the client side, causing an NPE in super.onUpdate()
|
||||
|
||||
this.prevPosX = this.posX;
|
||||
this.prevPosY = this.posY;
|
||||
this.prevPosZ = this.posZ;
|
||||
++this.fallTime;
|
||||
this.motionY -= 0.1d; //0.03999999910593033D;
|
||||
this.moveEntity(this.motionX, this.motionY, this.motionZ);
|
||||
this.motionX *= 0.9800000190734863D;
|
||||
this.motionY *= 0.9800000190734863D;
|
||||
this.motionZ *= 0.9800000190734863D;
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
if(this.onGround){
|
||||
|
||||
this.motionX *= 0.699999988079071D;
|
||||
this.motionZ *= 0.699999988079071D;
|
||||
this.motionY *= -0.5D;
|
||||
this.worldObj.createExplosion(this, this.posX, this.posY, this.posZ, 2.0f*blastMultiplier, true);
|
||||
for(int i1=-3; i1<4; i1++){
|
||||
for(int j1=-3; j1<4; j1++){
|
||||
int y = WizardryUtilities.getNearestFloorLevelB(this.worldObj, new BlockPos(this.posX + i1, this.posY, this.posZ + j1), 7);
|
||||
//System.out.println(y);
|
||||
double dist = this.getDistance((int)this.posX + i1, y, (int)this.posZ + j1);
|
||||
// Randomised with weighting so that the nearer the block the more likely it is to be set on fire.
|
||||
if(y != -1 && rand.nextInt((int)dist*2 + 1) < 3 && dist < 4){
|
||||
this.worldObj.setBlockState(new BlockPos(this.posX + i1, y, this.posZ + j1), Blocks.FIRE.getDefaultState());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier){
|
||||
// Don't need to do anything here, the meteor should have already exploded.
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public boolean canRenderOnFire(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getBlock(){
|
||||
return WizardryBlocks.meteor.getDefaultState(); // For some reason the superclass version returns null on the client
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public int getBrightnessForRender(float partialTicks){
|
||||
return 15728880;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks){
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInRangeToRenderDist(double distance){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
blastMultiplier = nbttagcompound.getFloat("blastMultiplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setFloat("blastMultiplier", blastMultiplier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package electroblob.wizardry.entity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.IProjectile;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityShield extends Entity {
|
||||
|
||||
public WeakReference<EntityPlayer> player;
|
||||
|
||||
public EntityShield(World world){
|
||||
super(world);
|
||||
this.noClip = true;
|
||||
this.width = 1.2f;
|
||||
this.height = 1.4f;
|
||||
}
|
||||
|
||||
public EntityShield(World par1World, EntityPlayer player) {
|
||||
super(par1World);
|
||||
this.width = 1.2f;
|
||||
this.height = 1.4f;
|
||||
this.player = new WeakReference<EntityPlayer>(player);
|
||||
this.noClip = true;
|
||||
this.setPositionAndRotation(player.posX + player.getLookVec().xCoord, player.posY + 1 + player.getLookVec().yCoord, player.posZ + player.getLookVec().zCoord, player.rotationYawHead, player.rotationPitch);
|
||||
this.setEntityBoundingBox(new AxisAlignedBB(this.posX - 0.6f, this.posY - 0.7f, this.posZ - 0.6f, this.posX + 0.6f, this.posY + 0.7f, this.posZ + 0.6f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
//System.out.println("Shield exists, ID: " + this.getUniqueID().toString());
|
||||
EntityPlayer entityplayer = player != null ? player.get() : null;
|
||||
if(entityplayer != null){
|
||||
this.setPositionAndRotation(entityplayer.posX + entityplayer.getLookVec().xCoord*0.3, entityplayer.posY + 1 + entityplayer.getLookVec().yCoord*0.3, entityplayer.posZ + entityplayer.getLookVec().zCoord*0.3, entityplayer.rotationYawHead, entityplayer.rotationPitch);
|
||||
if(!entityplayer.isHandActive() || entityplayer.getHeldItem(entityplayer.getActiveHand()) == null || !(entityplayer.getHeldItem(entityplayer.getActiveHand()).getItem() instanceof ItemWand)){
|
||||
WizardData.get(entityplayer).shield = null;
|
||||
this.setDead();
|
||||
}
|
||||
}else if(!worldObj.isRemote){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow
|
||||
// it to stick in blocks.
|
||||
public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9)
|
||||
{
|
||||
this.setPosition(par1, par3, par5);
|
||||
this.setRotation(par7, par8);
|
||||
}
|
||||
|
||||
public boolean attackEntityFrom(DamageSource par1DamageSource, float par2)
|
||||
{
|
||||
if(par1DamageSource != null && par1DamageSource.getSourceOfDamage() instanceof IProjectile){
|
||||
par1DamageSource.getSourceOfDamage().playSound(WizardrySounds.SPELL_DEFLECTION, 0.3f, 1.3f);
|
||||
}
|
||||
super.attackEntityFrom(par1DamageSource, par2);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canBeCollidedWith()
|
||||
{
|
||||
return !this.isDead;
|
||||
}
|
||||
|
||||
public AxisAlignedBB getCollisionBox(Entity par1Entity)
|
||||
{
|
||||
return par1Entity.getEntityBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.projectile.EntityTippedArrow;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityArrowRain extends EntityMagicConstruct {
|
||||
|
||||
public EntityArrowRain(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 3.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public EntityArrowRain(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.height = 3.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
EntityTippedArrow arrow = new EntityTippedArrow(worldObj, this.posX + rand.nextDouble()*6 - 3, this.posY + rand.nextDouble()*4 - 2, this.posZ + rand.nextDouble()*6 - 3);
|
||||
arrow.motionX = Math.cos(Math.toRadians(this.rotationYaw + 90));
|
||||
arrow.motionY = -0.6;
|
||||
arrow.motionZ = Math.sin(Math.toRadians(this.rotationYaw + 90));
|
||||
arrow.shootingEntity = this.getCaster();
|
||||
arrow.setDamage(7.0d*damageMultiplier);
|
||||
arrow.setPotionEffect(new ItemStack(Items.ARROW));
|
||||
this.worldObj.spawnEntityInWorld(arrow);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.play.server.SPacketEntityVelocity;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityBlackHole extends EntityMagicConstruct {
|
||||
|
||||
public int[] randomiser;
|
||||
public int[] randomiser2;
|
||||
|
||||
public EntityBlackHole(World world){
|
||||
super(world);
|
||||
this.width = 6.0f;
|
||||
this.height = 3.0f;
|
||||
randomiser = new int[30];
|
||||
for(int i=0; i<randomiser.length; i++){
|
||||
randomiser[i] = this.rand.nextInt(10);
|
||||
}
|
||||
randomiser2 = new int[30];
|
||||
for(int i=0; i<randomiser2.length; i++){
|
||||
randomiser2[i] = this.rand.nextInt(10);
|
||||
}
|
||||
}
|
||||
|
||||
public EntityBlackHole(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.width = 6.0f;
|
||||
this.height = 3.0f;
|
||||
randomiser = new int[30];
|
||||
for(int i=0; i<randomiser.length; i++){
|
||||
randomiser[i] = this.rand.nextInt(10);
|
||||
}
|
||||
randomiser2 = new int[30];
|
||||
for(int i=0; i<randomiser2.length; i++){
|
||||
randomiser2[i] = this.rand.nextInt(10);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound) {
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
randomiser = nbttagcompound.getIntArray("randomiser");
|
||||
randomiser2 = nbttagcompound.getIntArray("randomiser2");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound) {
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setIntArray("randomiser", randomiser);
|
||||
nbttagcompound.setIntArray("randomiser2", randomiser2);
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
//System.out.println("Client side: " + this.worldObj.isRemote + ", Caster: " + this.caster);
|
||||
|
||||
// Particle effect. Finishes 40 ticks before the end so the particles disappear at the same time.
|
||||
if(this.ticksExisted + 40 < this.lifetime){
|
||||
for (int i=0; i<5; i++){
|
||||
//this.worldObj.spawnParticle(EnumParticleTypes.PORTAL, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height - 0.75D, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, (this.rand.nextDouble() - 0.5D) * 2.0D, -this.rand.nextDouble(), (this.rand.nextDouble() - 0.5D) * 2.0D);
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.PORTAL, this.posX, this.posY, this.posZ, (this.rand.nextDouble() - 0.5D) * 4.0D, (this.rand.nextDouble() - 0.5D) * 4.0D - 1, (this.rand.nextDouble() - 0.5D) * 4.0D);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.lifetime - this.ticksExisted == 75){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_TRIGGER, 1.5f, 1.0f);
|
||||
}else if(this.ticksExisted % 80 == 1 && this.ticksExisted + 80 < this.lifetime){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.5f, 1.0f);
|
||||
}
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(6.0d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
// Sucks the target in
|
||||
if(this.posX > target.posX && target.motionX < 1){
|
||||
target.motionX+=0.1;
|
||||
}else if(this.posX < target.posX && target.motionX > -1){
|
||||
target.motionX-=0.1;
|
||||
}
|
||||
|
||||
if(this.posY > target.posY && target.motionY < 1){
|
||||
target.motionY+=0.1;
|
||||
}else if(this.posY < target.posY && target.motionY > -1){
|
||||
target.motionY-=0.1;
|
||||
}
|
||||
|
||||
if(this.posZ > target.posZ && target.motionZ < 1){
|
||||
target.motionZ+=0.1;
|
||||
}else if(this.posZ < target.posZ && target.motionZ > -1){
|
||||
target.motionZ-=0.1;
|
||||
}
|
||||
|
||||
// Player motion is handled on that player's client so needs packets
|
||||
if(target instanceof EntityPlayerMP){
|
||||
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
|
||||
}
|
||||
|
||||
if(this.getDistanceToEntity(target) <= 2){
|
||||
// Damages the target if it is close enough
|
||||
if(this.getCaster() != null){
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), 2*damageMultiplier);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.magic, 2*damageMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks using a Vec3dd to determine if this entity is within range of that vector to be rendered. Args: Vec3dD
|
||||
*/
|
||||
public boolean isInRangeToRenderVec3dD(Vec3d par1Vec3d)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityBlizzard extends EntityMagicConstruct {
|
||||
|
||||
public EntityBlizzard(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 1.0f;
|
||||
this.width = 1.0f;
|
||||
}
|
||||
|
||||
public EntityBlizzard(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.height = 1.0f;
|
||||
this.width = 1.0f;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 120 == 1){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(3.0d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
if(this.getCaster() != null){
|
||||
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.FROST), 1*damageMultiplier);
|
||||
}else{
|
||||
WizardryUtilities.attackEntityWithoutKnockback(target, DamageSource.magic, 1*damageMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
// All entities are slowed, even the caster (except those immune to frost effects)
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, target))
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 20, 0));
|
||||
}
|
||||
}else{
|
||||
// For some reason this number of particles now causes the game to lag significantly, despite it being fine
|
||||
// in 1.7.10. I thought particles were supposed to be LESS laggy now...
|
||||
for(int i=1; i<6; i++){
|
||||
float brightness = 0.5f + (rand.nextFloat()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.BLIZZARD, worldObj, this.posX, this.posY + rand.nextDouble()*3, this.posZ, 0, 0, 0, 100, brightness, brightness + 0.1f, 1.0f, false, rand.nextDouble() * 2.5d + 0.5d);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.BLIZZARD, worldObj, this.posX, this.posY + rand.nextDouble()*3, this.posZ, 0, 0, 0, 100, 1.0f, 1.0f, 1.0f, false, rand.nextDouble() * 2.5d + 0.5d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityBubble extends EntityMagicConstruct {
|
||||
|
||||
public boolean isDarkOrb;
|
||||
|
||||
private WeakReference<EntityLivingBase> rider;
|
||||
|
||||
public EntityBubble(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityBubble(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, boolean isDarkOrb, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
//this.setSize(0.1f, 0.1f);
|
||||
this.isDarkOrb = isDarkOrb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMountedYOffset()
|
||||
{
|
||||
return 0.1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRiderSit(){
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Synchronises the rider field
|
||||
if((this.rider == null || this.rider.get() == null) && WizardryUtilities.getRider(this) instanceof EntityLivingBase
|
||||
&& !WizardryUtilities.getRider(this).isDead){
|
||||
this.rider = new WeakReference<EntityLivingBase>((EntityLivingBase) WizardryUtilities.getRider(this));
|
||||
}
|
||||
|
||||
// Prevents dismounting
|
||||
if(WizardryUtilities.getRider(this) == null && this.rider != null && this.rider.get() != null && !this.rider.get().isDead){
|
||||
this.rider.get().startRiding(this);
|
||||
}
|
||||
|
||||
// Stops the bubble bursting instantly.
|
||||
if(this.ticksExisted < 1 && !isDarkOrb) ((EntityLivingBase)WizardryUtilities.getRider(this)).hurtTime = 0;
|
||||
|
||||
this.moveEntity(0, 0.03, 0);
|
||||
|
||||
if(isDarkOrb){
|
||||
|
||||
if(WizardryUtilities.getRider(this) != null && this.ticksExisted % 30 == 0){
|
||||
if(this.getCaster() != null){
|
||||
WizardryUtilities.getRider(this).attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), 1*damageMultiplier);
|
||||
}else{
|
||||
WizardryUtilities.getRider(this).attackEntityFrom(DamageSource.magic, 1*damageMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
for(int i=0; i<5; i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.PORTAL, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height + 0.5d, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, (this.rand.nextDouble() - 0.5D) * 2.0D, -this.rand.nextDouble(), (this.rand.nextDouble() - 0.5D) * 2.0D);
|
||||
}
|
||||
if(lifetime - this.ticksExisted == 75){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_TRIGGER, 1.5f, 1.0f);
|
||||
}else if(this.ticksExisted % 100 == 1 && this.ticksExisted < 150){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.5f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Bubble bursts if the entity is hurt (see event handler) or killed, or if the bubble has existed for more than 10 seconds.
|
||||
if(WizardryUtilities.getRider(this) == null && this.ticksExisted > 1){
|
||||
if(!this.isDarkOrb) this.playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f);
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void despawn(){
|
||||
if(WizardryUtilities.getRider(this) != null){
|
||||
((EntityLivingBase)WizardryUtilities.getRider(this)).dismountEntity(this);
|
||||
}
|
||||
if(!this.isDarkOrb) this.playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f);
|
||||
super.despawn();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound) {
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
isDarkOrb = nbttagcompound.getBoolean("isDarkOrb");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound) {
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setBoolean("isDarkOrb", isDarkOrb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data) {
|
||||
super.writeSpawnData(data);
|
||||
data.writeBoolean(this.isDarkOrb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data) {
|
||||
super.readSpawnData(data);
|
||||
this.isDarkOrb = data.readBoolean();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityDecay extends EntityMagicConstruct {
|
||||
|
||||
public int textureIndex = 0;
|
||||
public static final int LIFETIME = 400;
|
||||
|
||||
public EntityDecay(World par1World) {
|
||||
super(par1World);
|
||||
textureIndex = this.rand.nextInt(10);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
public EntityDecay(World par1World, double x, double y, double z, EntityLivingBase caster) {
|
||||
super(par1World, x, y, z, caster, LIFETIME, 1);
|
||||
textureIndex = this.rand.nextInt(10);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(this.rand.nextInt(700) == 0 && this.ticksExisted+100 < LIFETIME) this.playSound(SoundEvents.BLOCK_LAVA_AMBIENT, 0.2F + rand.nextFloat() * 0.2F, 0.6F + rand.nextFloat() * 0.15F);
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
for(EntityLivingBase target : targets){
|
||||
if(target != this.getCaster()){
|
||||
// If this check wasn't here the potion would be reapplied every tick and hence the entity would be damaged each tick.
|
||||
// In this case, we do want particles to be shown.
|
||||
if(!target.isPotionActive(WizardryPotions.decay)) target.addPotionEffect(new PotionEffect(WizardryPotions.decay, LIFETIME, 0));
|
||||
}
|
||||
}
|
||||
}else if(this.rand.nextInt(15) == 0){
|
||||
double radius = rand.nextDouble()*0.8;
|
||||
double angle = rand.nextDouble()*Math.PI*2;
|
||||
float brightness = rand.nextFloat()*0.4f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + radius*Math.cos(angle), this.posY, this.posZ + radius*Math.sin(angle), 0, 0, 0, 0, brightness, 0, brightness+0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
protected void entityInit(){}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks using a Vec3dd to determine if this entity is within range of that vector to be rendered. Args: Vec3dD
|
||||
*/
|
||||
public boolean isInRangeToRenderVec3dD(Vec3d par1Vec3d)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.item.EntityFallingBlock;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.network.play.server.SPacketEntityVelocity;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityEarthquake extends EntityMagicConstruct {
|
||||
|
||||
public EntityEarthquake(World world){
|
||||
super(world);
|
||||
this.height = 1.0f;
|
||||
this.width = 1.0f;
|
||||
}
|
||||
|
||||
public EntityEarthquake(World world, double x, double y, double z, EntityLivingBase caster, int lifetime,
|
||||
float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.height = 1.0f;
|
||||
this.width = 1.0f;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!worldObj.isRemote){
|
||||
|
||||
double speed = 0.4;
|
||||
|
||||
// The further the earthquake is going to spread, the finer the angle increments.
|
||||
for(double angle=0; angle < 2*Math.PI; angle+=Math.PI/(lifetime*1.5)){
|
||||
|
||||
// Calculates coordinates for the block to be moved. The radius increases with time. The +1.5 is to leave
|
||||
// blocks in the centre untouched.
|
||||
int x = this.posX < 0 ? (int)(this.posX + ((this.ticksExisted*speed)+1.5)*Math.sin(angle) - 1) : (int)(this.posX + ((this.ticksExisted*speed)+1.5)*Math.sin(angle));
|
||||
int y = (int)(this.posY - 0.5);
|
||||
int z = this.posZ < 0 ? (int)(this.posZ + ((this.ticksExisted*speed)+1.5)*Math.cos(angle) - 1) : (int)(this.posZ + ((this.ticksExisted*speed)+1.5)*Math.cos(angle));
|
||||
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
|
||||
if(!WizardryUtilities.isBlockUnbreakable(worldObj, pos) && !worldObj.isAirBlock(pos) && worldObj.isBlockNormalCube(pos, false)
|
||||
// Checks that the block above is not solid, since this causes the falling sand to vanish.
|
||||
&& !worldObj.isBlockNormalCube(pos.up(), false)){
|
||||
|
||||
// Falling blocks do the setting block to air themselves.
|
||||
EntityFallingBlock fallingblock = new EntityFallingBlock(worldObj, x+0.5, y+0.5, z+0.5, worldObj.getBlockState(new BlockPos(x, y, z)));
|
||||
fallingblock.motionY = 0.3;
|
||||
worldObj.spawnEntityInWorld(fallingblock);
|
||||
}
|
||||
}
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius((this.ticksExisted*speed)+1.5, this.posX, this.posY, this.posZ, worldObj);
|
||||
|
||||
// In this particular instance, the caster is completely unaffected because they will always be in the centre.
|
||||
targets.remove(this.getCaster());
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
// Searches in a 1 wide ring.
|
||||
if(this.getDistanceToEntity(target) > (this.ticksExisted*speed)+0.5 && target.posY < this.posY + 1 && target.posY > this.posY - 1){
|
||||
|
||||
// Knockback must be removed in this instance, or the target will fall into the floor.
|
||||
double motionX = target.motionX;
|
||||
double motionZ = target.motionZ;
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.BLAST), 10*this.damageMultiplier);
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 400, 1));
|
||||
}
|
||||
|
||||
// All targets are thrown, even those immune to the damage, so they don't fall into the ground.
|
||||
target.motionX = motionX;
|
||||
target.motionY = 0.8; // Throws target into the air.
|
||||
target.motionZ = motionZ;
|
||||
|
||||
// Player motion is handled on that player's client so needs packets
|
||||
if(target instanceof EntityPlayerMP){
|
||||
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Uncomment once 2.1.0 is released
|
||||
// }else{
|
||||
//
|
||||
// // Constant 15 blocks for now
|
||||
// List<EntityPlayer> targets = WizardryUtilities.getEntitiesWithinRadius(15, this.posX, this.posY, this.posZ, worldObj, EntityPlayer.class);
|
||||
//
|
||||
// float magnitude = 6f * ((float)(this.lifetime - this.ticksExisted))/(float)this.lifetime;
|
||||
//
|
||||
// // Makes the screen shake
|
||||
// for(EntityLivingBase target : targets){
|
||||
// target.setAngles(0, this.ticksExisted % 4 < 2 ? magnitude : -magnitude);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityFireRing extends EntityMagicConstruct {
|
||||
|
||||
public EntityFireRing(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 1.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public EntityFireRing(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.height = 1.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 40 == 1){
|
||||
this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 4.0f, 0.7f);
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(2.5d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
double velX = target.motionX;
|
||||
double velY = target.motionY;
|
||||
double velZ = target.motionZ;
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)){
|
||||
|
||||
target.setFire(10);
|
||||
|
||||
if(this.getCaster() != null){
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.FIRE), 1*damageMultiplier);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.magic, 1*damageMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes knockback
|
||||
target.motionX = velX;
|
||||
target.motionY = velY;
|
||||
target.motionZ = velZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityFireSigil extends EntityMagicConstruct {
|
||||
|
||||
public EntityFireSigil(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
public EntityFireSigil(World par1World, double x, double y, double z, EntityLivingBase caster, float damageMultiplier) {
|
||||
super(par1World, x, y, z, caster, -1, damageMultiplier);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
// Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow
|
||||
// it to stick in blocks.
|
||||
public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9)
|
||||
{
|
||||
this.setPosition(par1, par3, par5);
|
||||
this.setRotation(par7, par8);
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
double velX = target.motionX;
|
||||
double velY = target.motionY;
|
||||
double velZ = target.motionZ;
|
||||
|
||||
target.attackEntityFrom(this.getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.FIRE) : DamageSource.magic, 6);
|
||||
|
||||
// Removes knockback
|
||||
target.motionX = velX;
|
||||
target.motionY = velY;
|
||||
target.motionZ = velZ;
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) target.setFire(10);
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
|
||||
|
||||
// The trap is destroyed once triggered.
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}else if(this.rand.nextInt(15) == 0){
|
||||
double radius = 0.5 + rand.nextDouble()*0.3;
|
||||
double angle = rand.nextDouble()*Math.PI*2;
|
||||
worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + radius*Math.cos(angle), this.posY + 0.1, this.posZ + radius*Math.sin(angle), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.network.play.server.SPacketEntityVelocity;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityForcefield extends EntityMagicConstruct {
|
||||
|
||||
public EntityForcefield(World world) {
|
||||
super(world);
|
||||
this.height = 6;
|
||||
this.width = 6;
|
||||
this.setEntityBoundingBox(new AxisAlignedBB(this.posX - 3, this.posY - 3, this.posZ - 3, this.posX + 3, this.posY + 3, this.posZ + 3));
|
||||
}
|
||||
|
||||
public EntityForcefield(World world, double x, double y, double z, EntityLivingBase caster, int lifetime) {
|
||||
// y-3 because it needs to be centred on the given position
|
||||
// Damage multiplier is 1 because forcefields do no damage!
|
||||
super(world, x, y-3, z, caster, lifetime, 1.0f);
|
||||
this.height = 6;
|
||||
this.width = 6;
|
||||
this.setEntityBoundingBox(new AxisAlignedBB(this.posX - 3, this.posY - 3, this.posZ - 3, this.posX + 3, this.posY + 3, this.posZ + 3));
|
||||
}
|
||||
|
||||
public boolean canBeCollidedWith()
|
||||
{
|
||||
return !this.isDead;
|
||||
}
|
||||
|
||||
public AxisAlignedBB getCollisionBox(Entity par1Entity)
|
||||
{
|
||||
return par1Entity.getEntityBoundingBox();
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(3.5, this.posX, this.posY + 3, this.posZ, this.worldObj);
|
||||
for(EntityLivingBase target : targets){
|
||||
if(this.isValidTarget(target)){
|
||||
double multiplier = (3.5 - target.getDistance(this.posX, this.posY+3, this.posZ))*0.1;
|
||||
target.addVelocity((target.posX - this.posX)*multiplier, (target.posY - (this.posY + 3))*multiplier, (target.posZ - this.posZ)*multiplier);
|
||||
// Player motion is handled on that player's client so needs packets
|
||||
if(target instanceof EntityPlayerMP){
|
||||
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(int i=1; i<40; i++){
|
||||
float brightness = 0.5f + (rand.nextFloat()*0.5f);
|
||||
double radius = 3;
|
||||
double yaw = rand.nextDouble()*Math.PI*2;
|
||||
double pitch = (rand.nextDouble()-0.5)*Math.PI;
|
||||
// Generates a spherical pattern of particles
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.BRIGHT_DUST, worldObj, this.posX + radius*Math.cos(yaw)*Math.cos(pitch), this.posY + 3 + radius*Math.sin(pitch), this.posZ + radius*Math.sin(yaw)*Math.cos(pitch), 0, 0, 0, 48 + this.rand.nextInt(12), brightness, brightness, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean attackEntityFrom(DamageSource source, float par2){
|
||||
|
||||
if(source != null && source.getSourceOfDamage() != null){
|
||||
// Now works for any source of damage.
|
||||
source.getSourceOfDamage().playSound(WizardrySounds.SPELL_DEFLECTION, 0.3f, 1.3f);
|
||||
}
|
||||
super.attackEntityFrom(source, par2);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityFrostSigil extends EntityMagicConstruct {
|
||||
|
||||
public EntityFrostSigil(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
public EntityFrostSigil(World par1World, double x, double y, double z, EntityLivingBase caster, float damageMultiplier) {
|
||||
super(par1World, x, y, z, caster, -1, damageMultiplier);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
// Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow
|
||||
// it to stick in blocks.
|
||||
public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9)
|
||||
{
|
||||
this.setPosition(par1, par3, par5);
|
||||
this.setRotation(par7, par8);
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
double velX = target.motionX;
|
||||
double velY = target.motionY;
|
||||
double velZ = target.motionZ;
|
||||
|
||||
target.attackEntityFrom(this.getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.FROST) : DamageSource.magic, 8);
|
||||
|
||||
// Removes knockback
|
||||
target.motionX = velX;
|
||||
target.motionY = velY;
|
||||
target.motionZ = velZ;
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, target))
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 1));
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f);
|
||||
|
||||
// The trap is destroyed once triggered.
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}else if(this.rand.nextInt(15) == 0){
|
||||
double radius = 0.5 + rand.nextDouble()*0.3;
|
||||
double angle = rand.nextDouble()*Math.PI*2;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, worldObj, this.posX + radius*Math.cos(angle), this.posY + 0.1, this.posZ + radius*Math.sin(angle), 0, 0, 0, 40 + rand.nextInt(10));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import electroblob.wizardry.entity.projectile.EntityIceShard;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityHailstorm extends EntityMagicConstruct {
|
||||
|
||||
public EntityHailstorm(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 3.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public EntityHailstorm(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.height = 3.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
//System.out.println(this.rotationYaw);
|
||||
EntityIceShard iceshard = new EntityIceShard(worldObj, this.posX + rand.nextDouble()*6 - 3, this.posY + rand.nextDouble()*4 - 2, this.posZ + rand.nextDouble()*6 - 3);
|
||||
iceshard.motionX = Math.cos(Math.toRadians(this.rotationYaw + 90));
|
||||
iceshard.motionY = -0.6;
|
||||
iceshard.motionZ = Math.sin(Math.toRadians(this.rotationYaw + 90));
|
||||
iceshard.setShootingEntity(this.getCaster());
|
||||
iceshard.damageMultiplier = this.damageMultiplier;
|
||||
this.worldObj.spawnEntityInWorld(iceshard);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.entity.EntityArc;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.effect.EntityLightningBolt;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityHammer extends EntityMagicConstruct {
|
||||
|
||||
/** How long the hammer has been falling for. */
|
||||
public int fallTime;
|
||||
|
||||
public EntityHammer(World par1World){
|
||||
super(par1World);
|
||||
this.setSize(1.0f, 1.9F);
|
||||
this.noClip = false;
|
||||
}
|
||||
|
||||
public EntityHammer(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier){
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.setSize(1.0f, 1.9F);
|
||||
this.motionX = 0.0D;
|
||||
this.motionY = 0.0D;
|
||||
this.motionZ = 0.0D;
|
||||
this.noClip = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurning(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeCollidedWith(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getCollisionBoundingBox(){
|
||||
return this.getEntityBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(this.ticksExisted % 20 == 1 && !this.onGround && worldObj.isRemote){
|
||||
// Though this sound does repeat, it stops when it hits the ground.
|
||||
Wizardry.proxy.playMovingSound(this, WizardrySounds.SPELL_LOOP_LIGHTNING, 3.0f, 1.0f, false);
|
||||
}
|
||||
|
||||
if(this.worldObj.isRemote && this.ticksExisted % 3 == 0){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX - 0.5d + rand.nextDouble(), this.posY + 2*rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0, 0, 3);
|
||||
}
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
this.prevPosX = this.posX;
|
||||
this.prevPosY = this.posY;
|
||||
this.prevPosZ = this.posZ;
|
||||
++this.fallTime;
|
||||
this.motionY -= 0.03999999910593033D;
|
||||
this.moveEntity(this.motionX, this.motionY, this.motionZ);
|
||||
this.motionX *= 0.9800000190734863D;
|
||||
this.motionY *= 0.9800000190734863D;
|
||||
this.motionZ *= 0.9800000190734863D;
|
||||
|
||||
if(this.onGround){
|
||||
|
||||
this.motionX *= 0.699999988079071D;
|
||||
this.motionZ *= 0.699999988079071D;
|
||||
this.motionY *= -0.5D;
|
||||
|
||||
if(this.ticksExisted % 40 == 0){
|
||||
|
||||
double seekerRange = 10.0d;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, this.posY+1, this.posZ, worldObj);
|
||||
|
||||
// For this spell there is no limit to the amount of secondary targets!
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
if(!worldObj.isRemote){
|
||||
EntityArc arc = new EntityArc(worldObj);
|
||||
arc.setEndpointCoords(this.posX, this.posY + this.height - 0.1, this.posZ,
|
||||
target.posX, target.posY + target.height/2, target.posZ);
|
||||
worldObj.spawnEntityInWorld(arc);
|
||||
}else{
|
||||
for(int j=0;j<8;j++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, target.posX + worldObj.rand.nextFloat() - 0.5, target.getEntityBoundingBox().minY + target.height/2 + worldObj.rand.nextFloat()*2 - 1, target.posZ + worldObj.rand.nextFloat() - 0.5, 0, 0, 0, 3);
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, target.posX + rand.nextFloat(), target.getEntityBoundingBox().minY + target.height/2 + rand.nextFloat(), target.posZ + rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, rand.nextFloat() * 0.4F + 1.5F);
|
||||
|
||||
if(this.getCaster() != null){
|
||||
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), 6*damageMultiplier);
|
||||
WizardryUtilities.applyStandardKnockback(this, target);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.magic, 6*damageMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void despawn(){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_EXPLODE, 1.0F, 1.0f);
|
||||
|
||||
if(this.worldObj.isRemote){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
}
|
||||
|
||||
super.despawn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier) {
|
||||
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<40; i++){
|
||||
double particleX = this.posX - 1.0d + 2*rand.nextDouble();
|
||||
double particleZ = this.posZ - 1.0d + 2*rand.nextDouble();
|
||||
// Roundabout way of getting a block instance for the block the hammer is standing on (if any).
|
||||
IBlockState block = worldObj.getBlockState(new BlockPos(this.posX, this.posY-2, this.posZ));
|
||||
|
||||
if(block != null){
|
||||
worldObj.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, this.posY, particleZ,
|
||||
particleX - this.posX, 0, particleZ - this.posZ, Block.getStateId(block));
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// Just to check the hammer has actually fallen from the sky, rather than the block under it being broken.
|
||||
if(this.fallDistance > 10){
|
||||
EntityLightningBolt entitylightning = new EntityLightningBolt(worldObj, this.posX, this.posY, this.posZ, false);
|
||||
worldObj.addWeatherEffect(entitylightning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound)
|
||||
{
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setByte("Time", (byte)this.fallTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound)
|
||||
{
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.fallTime = nbttagcompound.getByte("Time") & 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInRangeToRenderDist(double distance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityHealAura extends EntityMagicConstruct {
|
||||
|
||||
public EntityHealAura(World world) {
|
||||
super(world);
|
||||
this.height = 1.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public EntityHealAura(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.height = 1.0f;
|
||||
this.width = 5.0f;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 25 == 1){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_SPARKLE, 0.1f, 1.0f);
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(2.5d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
if(target.isEntityUndead()){
|
||||
|
||||
double velX = target.motionX;
|
||||
double velY = target.motionY;
|
||||
double velZ = target.motionZ;
|
||||
|
||||
if(this.getCaster() != null){
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.RADIANT), 1*damageMultiplier);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.magic, 1*damageMultiplier);
|
||||
}
|
||||
|
||||
// Removes knockback
|
||||
target.motionX = velX;
|
||||
target.motionY = velY;
|
||||
target.motionZ = velZ;
|
||||
}
|
||||
|
||||
}else if(target.getHealth() < target.getMaxHealth() && this.ticksExisted % 5 == 0){
|
||||
target.heal(1*damageMultiplier);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(int i=1; i<3; i++){
|
||||
float brightness = 0.5f + (rand.nextFloat()*0.5f);
|
||||
double radius = rand.nextDouble()*2.0;
|
||||
double angle = rand.nextDouble()*Math.PI*2;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX + radius*Math.cos(angle), this.posY, this.posZ + radius*Math.sin(angle), 0, 0.05f, 0, 48 + this.rand.nextInt(12), 1.0f, 1.0f, brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceSpike extends EntityMagicConstruct {
|
||||
|
||||
public EntityIceSpike(World world) {
|
||||
super(world);
|
||||
this.setSize(0.5f, 1.0f);
|
||||
}
|
||||
|
||||
public EntityIceSpike(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier){
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.setSize(0.5f, 1.0f);
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
if(lifetime - this.ticksExisted < 15){
|
||||
this.motionY = -0.01*(this.ticksExisted-(lifetime-15));
|
||||
}else if(lifetime - this.ticksExisted < 25){
|
||||
this.motionY = 0;
|
||||
}else if(lifetime - this.ticksExisted < 28){
|
||||
this.motionY = 0.25;
|
||||
}
|
||||
|
||||
this.moveEntity(this.motionX, this.motionY, this.motionZ);
|
||||
|
||||
if(lifetime - this.ticksExisted == 30) this.playSound(WizardrySounds.SPELL_ICE, 1, 2);
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
for(Object entity : this.worldObj.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox())){
|
||||
if(entity instanceof EntityLivingBase && this.isValidTarget((EntityLivingBase)entity)){
|
||||
// Potion effect only gets added if the damage succeeded.
|
||||
if(((EntityLivingBase)entity).attackEntityFrom(MagicDamage.causeDirectMagicDamage(this.getCaster(), DamageType.FROST), 5*this.damageMultiplier))
|
||||
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityLightningPulse extends EntityMagicConstruct {
|
||||
|
||||
public EntityLightningPulse(World world) {
|
||||
super(world);
|
||||
this.setSize(6, 0.2f);
|
||||
}
|
||||
|
||||
public EntityLightningPulse(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.setSize(6, 0.2f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.entity.EntityArc;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityLightningSigil extends EntityMagicConstruct {
|
||||
|
||||
public EntityLightningSigil(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
public EntityLightningSigil(World par1World, double x, double y, double z, EntityLivingBase caster, float damageMultiplier) {
|
||||
super(par1World, x, y, z, caster, -1, damageMultiplier);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
// Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow
|
||||
// it to stick in blocks.
|
||||
public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9)
|
||||
{
|
||||
this.setPosition(par1, par3, par5);
|
||||
this.setRotation(par7, par8);
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(this.ticksExisted > 600 && this.getCaster() == null && !this.worldObj.isRemote){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
//if(!this.worldObj.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
double velX = target.motionX;
|
||||
double velY = target.motionY;
|
||||
double velZ = target.motionZ;
|
||||
|
||||
// Only works if target is actually damaged to account for hurtResistantTime
|
||||
if(target.attackEntityFrom(getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK) : DamageSource.magic, 6)){
|
||||
|
||||
// Removes knockback
|
||||
target.motionX = velX;
|
||||
target.motionY = velY;
|
||||
target.motionZ = velZ;
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_SPARK, 1.0f, 1.0f);
|
||||
|
||||
// Secondary chaining effect
|
||||
double seekerRange = 5.0d;
|
||||
|
||||
List<EntityLivingBase> secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, target.posX, target.posY + target.height/2, target.posZ, worldObj);
|
||||
|
||||
for(int j=0;j<Math.min(secondaryTargets.size(), 3);j++){
|
||||
|
||||
EntityLivingBase secondaryTarget = secondaryTargets.get(j);
|
||||
|
||||
if(secondaryTarget != target && this.isValidTarget(secondaryTarget)){
|
||||
|
||||
if(!worldObj.isRemote){
|
||||
EntityArc arc = new EntityArc(worldObj);
|
||||
arc.setEndpointCoords(target.posX, target.posY + target.height/2, target.posZ,
|
||||
secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height/2, secondaryTarget.posZ);
|
||||
worldObj.spawnEntityInWorld(arc);
|
||||
}else{
|
||||
for(int k=0;k<8;k++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, secondaryTarget.posX + worldObj.rand.nextFloat() - 0.5, secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height/2 + worldObj.rand.nextFloat()*2 - 1, secondaryTarget.posZ + worldObj.rand.nextFloat() - 0.5, 0, 0, 0, 3);
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, secondaryTarget.posX + worldObj.rand.nextFloat() - 0.5, secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height/2 + worldObj.rand.nextFloat()*2 - 1, secondaryTarget.posZ + worldObj.rand.nextFloat() - 0.5, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
secondaryTarget.playSound(WizardrySounds.SPELL_SPARK, 1.0F, worldObj.rand.nextFloat() * 0.4F + 1.5F);
|
||||
|
||||
secondaryTarget.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), 4);
|
||||
}
|
||||
|
||||
}
|
||||
// The trap is destroyed once triggered.
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
if(this.worldObj.isRemote && this.rand.nextInt(15) == 0){
|
||||
double radius = 0.5 + rand.nextDouble()*0.3;
|
||||
double angle = rand.nextDouble()*Math.PI*2;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + radius*Math.cos(angle), this.posY + 0.1, this.posZ + radius*Math.sin(angle), 0, 0, 0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
/**
|
||||
* This class is for all inanimate magical constructs which are not projectiles. It was made from scratch
|
||||
* to provide a unifying superclass for black hole, blizzard, tornado and a few others which all share some
|
||||
* characteristics. The EntityPlayer instance of the caster, the lifetime and the damage multiplier are stored
|
||||
* and synced here.
|
||||
* <p>
|
||||
* When extending this class, override both constructors. Generally speaking, subclasses of this class are areas
|
||||
* of effect which deal damage or apply effects over time.
|
||||
* @since Wizardry 1.0
|
||||
*/
|
||||
public abstract class EntityMagicConstruct extends Entity implements IEntityAdditionalSpawnData {
|
||||
|
||||
/** The entity that created this construct */
|
||||
private WeakReference<EntityLivingBase> caster;
|
||||
|
||||
/** The UUID of the caster. Note that this is only for loading purposes; during normal updates
|
||||
* the actual entity instance is stored (so that getEntityByUUID is not called constantly),
|
||||
* so this will not always be synced (this is why it is private). */
|
||||
private UUID casterUUID;
|
||||
|
||||
/** The time in ticks this magical construct lasts for; defaults to 600 (30 seconds). If this is -1 the construct
|
||||
* doesn't despawn. */
|
||||
public int lifetime = 600;
|
||||
|
||||
/** The damage multiplier for this construct, determined by the wand with which it was cast. */
|
||||
public float damageMultiplier = 1.0f;
|
||||
|
||||
public EntityMagicConstruct(World par1World) {
|
||||
super(par1World);
|
||||
this.height = 1.0f;
|
||||
this.width = 1.0f;
|
||||
this.noClip = true;
|
||||
}
|
||||
|
||||
public EntityMagicConstruct(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, float damageMultiplier) {
|
||||
super(world);
|
||||
this.height = 1.0f;
|
||||
this.width = 1.0f;
|
||||
this.setPosition(x, y, z);
|
||||
this.caster = new WeakReference<EntityLivingBase>(caster);
|
||||
this.noClip = true;
|
||||
this.lifetime = lifetime;
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
}
|
||||
|
||||
// Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow
|
||||
// it to stick in blocks.
|
||||
@Override
|
||||
public void setPositionAndRotationDirect(double x, double y, double z, float yaw, float pitch, int posRotationIncrements, boolean teleport)
|
||||
{
|
||||
this.setPosition(x, y, z);
|
||||
this.setRotation(yaw, pitch);
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.getCaster() == null && this.casterUUID != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(worldObj, casterUUID);
|
||||
if(entity instanceof EntityLivingBase){
|
||||
this.caster = new WeakReference<EntityLivingBase>((EntityLivingBase)entity);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > lifetime && lifetime != -1){
|
||||
this.despawn();
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to just setDead() in EntityMagicConstruct, but is provided to allow subclasses to override this
|
||||
* e.g. bubble uses it to dismount the entity inside it and play the 'pop' sound before calling super(). You
|
||||
* should always call super() when overriding this method, in case it changes. There is no need, therefore, to
|
||||
* call setDead() when overriding.
|
||||
*/
|
||||
public void despawn(){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
casterUUID = nbttagcompound.getUniqueId("casterUUID");
|
||||
lifetime = nbttagcompound.getInteger("lifetime");
|
||||
damageMultiplier = nbttagcompound.getFloat("damageMultiplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
if(this.getCaster() != null){
|
||||
nbttagcompound.setUniqueId("casterUUID", this.getCaster().getUniqueID());
|
||||
}
|
||||
nbttagcompound.setInteger("lifetime", lifetime);
|
||||
nbttagcompound.setFloat("damageMultiplier", damageMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
data.writeInt(lifetime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
lifetime = data.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the EntityLivingBase that created this construct, or null if it no longer exists. Cases where the
|
||||
* entity may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported
|
||||
* to another dimension, or this construct simply had no caster in the first place.
|
||||
*/
|
||||
public EntityLivingBase getCaster() {
|
||||
return caster == null ? null : caster.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this construct as the
|
||||
* attacker. Also allows subclasses to override it if they wish to do so.
|
||||
*/
|
||||
public boolean isValidTarget(Entity target){
|
||||
return WizardryUtilities.isValidTarget(this.getCaster(), target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushedByWater() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryAchievements;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.passive.EntityPig;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.play.server.SPacketEntityVelocity;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityTornado extends EntityMagicConstruct {
|
||||
|
||||
private double velX, velZ;
|
||||
|
||||
public EntityTornado(World world) {
|
||||
super(world);
|
||||
this.height = 8.0f;
|
||||
this.width = 5.0f;
|
||||
this.isImmuneToFire = false;
|
||||
}
|
||||
|
||||
public EntityTornado(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, double velX, double velZ, float damageMultiplier) {
|
||||
super(world, x, y, z, caster, lifetime, damageMultiplier);
|
||||
this.height = 8.0f;
|
||||
this.width = 5.0f;
|
||||
this.velX = velX;
|
||||
this.velZ = velZ;
|
||||
this.isImmuneToFire = false;
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(this.ticksExisted % 120 == 1 && worldObj.isRemote){
|
||||
// Repeat is false so that the sound fades out when the tornado does rather than stopping suddenly
|
||||
Wizardry.proxy.playMovingSound(this, WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f, false);
|
||||
}
|
||||
|
||||
this.moveEntity(velX, motionY, velZ);
|
||||
|
||||
BlockPos pos = new BlockPos(this);
|
||||
int y = WizardryUtilities.getNearestFloorLevelC(worldObj, pos.up(3), 5);
|
||||
pos = new BlockPos(pos.getX(), y, pos.getZ());
|
||||
|
||||
if(this.worldObj.getBlockState(pos).getMaterial() == Material.LAVA){
|
||||
// Fire tornado!
|
||||
this.setFire(5);
|
||||
}
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(4.0d, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
double velY = target.motionY;
|
||||
|
||||
double dx = this.posX-target.posX > 0? 0.5 - (this.posX-target.posX)/8 : -0.5 - (this.posX-target.posX)/8;
|
||||
double dz = this.posZ-target.posZ > 0? 0.5 - (this.posZ-target.posZ)/8 : -0.5 - (this.posZ-target.posZ)/8;
|
||||
|
||||
if(this.isBurning()){
|
||||
target.setFire(4);
|
||||
}
|
||||
|
||||
if(this.getCaster() != null){
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), 1*damageMultiplier);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.magic, 1*damageMultiplier);
|
||||
}
|
||||
|
||||
target.motionX = dx;
|
||||
target.motionY = velY+0.2;
|
||||
target.motionZ = dz;
|
||||
|
||||
// Player motion is handled on that player's client so needs packets
|
||||
if(target instanceof EntityPlayerMP){
|
||||
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
|
||||
}
|
||||
|
||||
// The 'Not Again...' achievement
|
||||
if(target instanceof EntityPig && WizardryUtilities.getRider(target) instanceof EntityPlayer){
|
||||
((EntityPlayer)WizardryUtilities.getRider(target)).addStat(WizardryAchievements.pig_tornado);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(int i=1; i<10; i++){
|
||||
|
||||
double yPos = rand.nextDouble()*8;
|
||||
|
||||
int blockX = (int)this.posX - 2 + this.rand.nextInt(4);
|
||||
int blockZ = (int)this.posZ - 2 + this.rand.nextInt(4);
|
||||
|
||||
BlockPos pos1 = new BlockPos(blockX, this.posY+3, blockZ);
|
||||
|
||||
int blockY = WizardryUtilities.getNearestFloorLevelC(worldObj, pos1, 5) - 1;
|
||||
|
||||
pos1 = new BlockPos(pos1.getX(), blockY, pos1.getZ());
|
||||
|
||||
IBlockState block = this.worldObj.getBlockState(pos1);
|
||||
|
||||
// If the block it found was air or something it can't pick up, it makes a best guess based on the biome.
|
||||
if(!canTornadoPickUpBitsOf(block)){
|
||||
block = worldObj.getBiome(pos1).topBlock;
|
||||
}
|
||||
|
||||
Wizardry.proxy.spawnTornadoParticle(worldObj, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ, yPos/3 + 0.5d, 100, block, pos1);
|
||||
Wizardry.proxy.spawnTornadoParticle(worldObj, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ, yPos/3 + 0.5d, 100, block, pos1);
|
||||
|
||||
// Sometimes spawns leaf particles if the block is leaves
|
||||
if(block.getMaterial() == Material.LEAVES && this.rand.nextInt(3) == 0){
|
||||
double yPos1 = rand.nextDouble()*8;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.LEAF, worldObj, this.posX + (rand.nextDouble()*2-1)*(yPos1/3 + 0.5d), this.posY + yPos1, this.posZ + (rand.nextDouble()*2-1)*(yPos1/3 + 0.5d), 0, -0.05, 0, 40 + rand.nextInt(10));
|
||||
}
|
||||
|
||||
// Sometimes spawns snow particles if the block is snow
|
||||
if(block.getMaterial() == Material.SNOW || block.getMaterial() == Material.CRAFTED_SNOW && this.rand.nextInt(3) == 0){
|
||||
double yPos1 = rand.nextDouble()*8;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, worldObj, this.posX + (rand.nextDouble()*2-1)*(yPos1/3 + 0.5d), this.posY + yPos1, this.posZ + (rand.nextDouble()*2-1)*(yPos1/3 + 0.5d), 0, -0.02, 0, 40 + rand.nextInt(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canTornadoPickUpBitsOf(IBlockState block){
|
||||
Material material = block.getMaterial();
|
||||
return material == Material.CRAFTED_SNOW
|
||||
|| material == Material.GROUND
|
||||
|| material == Material.GRASS
|
||||
|| material == Material.LAVA
|
||||
|| material == Material.SAND
|
||||
|| material == Material.SNOW
|
||||
|| material == Material.WATER
|
||||
|| material == Material.PLANTS
|
||||
|| material == Material.LEAVES
|
||||
|| material == Material.VINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound) {
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
velX = nbttagcompound.getDouble("velX");
|
||||
velZ = nbttagcompound.getDouble("velZ");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound) {
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setDouble("velX", velX);
|
||||
nbttagcompound.setDouble("velZ", velZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data) {
|
||||
super.writeSpawnData(data);
|
||||
data.writeDouble(velX);
|
||||
data.writeDouble(velZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data) {
|
||||
super.readSpawnData(data);
|
||||
this.velX = data.readDouble();
|
||||
this.velZ = data.readDouble();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.packet.PacketNPCCastSpell;
|
||||
import electroblob.wizardry.packet.WizardryPacketHandler;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
|
||||
|
||||
/** Entity AI class for use by instances of {@link ISpellCaster}. This deals with pathing, the spell casting itself
|
||||
* and the attack cooldown. Also provides an automatic implementation of continuous spell casting using the methods
|
||||
* specified in {@code ISpellCaster}; all the entity class needs to do is implement those methods. */
|
||||
public class EntityAIAttackSpell extends EntityAIBase {
|
||||
|
||||
/** The entity the AI instance has been applied to. */
|
||||
private final EntityLiving attacker;
|
||||
/** The entity the AI instance has been applied to, but as an ISpellCaster. */
|
||||
private final ISpellCaster caster;
|
||||
/** The tagret to be attacked. */
|
||||
private EntityLivingBase target;
|
||||
/** Decremented each tick while greater than 0. When a spell is cast, this is set to that spell's cooldown plus
|
||||
* the base cooldown. */
|
||||
private int cooldown;
|
||||
/** The number of ticks between the entity finding a new target and when it first starts attacking, and also the
|
||||
* amount that is added to the spell's cooldown between casting spells. */
|
||||
private final int baseCooldown;
|
||||
/** Decremented each tick while greater than 0. When a continuous spell is first cast, this is set to
|
||||
* the value of {@link EntityAIAttackSpell#continuousSpellDuration}. */
|
||||
// I think that in this case this is only necessary on the server side. If any inconsistent behaviour
|
||||
// occurs, look into syncing this as well.
|
||||
private int continuousSpellTimer;
|
||||
/** The number of ticks that continuous spells will be cast for before cooling down. */
|
||||
private final int continuousSpellDuration;
|
||||
/** The speed that the entity should move when attacking. Only used when passed into the navigator. */
|
||||
private final double speed;
|
||||
private int seeTime;
|
||||
private final float maxAttackDistance;
|
||||
|
||||
/**
|
||||
* Creates a new spell attack AI with the given parameters.
|
||||
* @param attacker The entity that that uses this AI.
|
||||
* @param speed The speed that the entity should move when attacking. Only used when passed into the navigator.
|
||||
* @param maxDistance The maximum distance the entity should be from its target.
|
||||
* @param baseCooldown The number of ticks between the entity finding a new target and when it first starts attacking,
|
||||
* and also the amount that is added to the cooldown of the spell that has just been cast.
|
||||
* @param continuousSpellDuration The number of ticks that continuous spells will be cast for before cooling down.
|
||||
*/
|
||||
public EntityAIAttackSpell(ISpellCaster attacker, double speed, float maxDistance, int baseCooldown, int continuousSpellDuration){
|
||||
|
||||
this.cooldown = -1;
|
||||
|
||||
if(!(attacker instanceof EntityLiving)){
|
||||
throw new IllegalArgumentException("Tried to create an EntityAICastSpell for an entity that isn't an EntityLiving");
|
||||
}else{
|
||||
this.caster = attacker;
|
||||
this.attacker = (EntityLiving)attacker;
|
||||
this.baseCooldown = baseCooldown;
|
||||
this.continuousSpellDuration = continuousSpellDuration;
|
||||
this.speed = speed;
|
||||
this.maxAttackDistance = maxDistance * maxDistance;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExecute(){
|
||||
|
||||
EntityLivingBase entitylivingbase = this.attacker.getAttackTarget();
|
||||
|
||||
if(entitylivingbase == null){
|
||||
return false;
|
||||
}else{
|
||||
this.target = entitylivingbase;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueExecuting(){
|
||||
return this.shouldExecute() || !this.attacker.getNavigator().noPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetTask(){
|
||||
this.target = null;
|
||||
this.seeTime = 0;
|
||||
this.cooldown = -1;
|
||||
this.setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.continuousSpellTimer = 0;
|
||||
}
|
||||
|
||||
private void setContinuousSpellAndNotify(Spell spell, SpellModifiers modifiers){
|
||||
caster.setContinuousSpell(spell);
|
||||
WizardryPacketHandler.net.sendToAllAround(new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target == null ? -1 : target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers),
|
||||
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
|
||||
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTask(){
|
||||
|
||||
// Only executed server side.
|
||||
|
||||
double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.getEntityBoundingBox().minY, this.target.posZ);
|
||||
boolean targetIsVisible = this.attacker.getEntitySenses().canSee(this.target);
|
||||
|
||||
if(targetIsVisible){
|
||||
++this.seeTime;
|
||||
}else{
|
||||
this.seeTime = 0;
|
||||
}
|
||||
|
||||
if(distanceSq <= (double)this.maxAttackDistance && this.seeTime >= 20){
|
||||
this.attacker.getNavigator().clearPathEntity();
|
||||
}else{
|
||||
this.attacker.getNavigator().tryMoveToEntityLiving(this.target, this.speed);
|
||||
}
|
||||
|
||||
this.attacker.getLookHelper().setLookPositionWithEntity(this.target, 30.0F, 30.0F);
|
||||
|
||||
if(this.continuousSpellTimer > 0){
|
||||
|
||||
this.continuousSpellTimer--;
|
||||
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
|
||||
|| !caster.getContinuousSpell().cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND,
|
||||
this.continuousSpellDuration - this.continuousSpellTimer, target, caster.getModifiers())
|
||||
|| this.continuousSpellTimer == 0){
|
||||
// If the spell no longer succeeds, the target goes out of range or sight, or the time has elapsed,
|
||||
// reset the continuous spell timer and start the cooldown.
|
||||
this.continuousSpellTimer = 0;
|
||||
setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.cooldown = this.baseCooldown;
|
||||
return;
|
||||
}
|
||||
|
||||
}else if(--this.cooldown == 0){
|
||||
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!attacker.isPotionActive(WizardryPotions.arcane_jammer)){
|
||||
|
||||
double dx = target.posX - attacker.posX;
|
||||
double dz = target.posZ - attacker.posZ;
|
||||
|
||||
List<Spell> spells = new ArrayList<Spell>(caster.getSpells());
|
||||
|
||||
if(spells.size() > 0){
|
||||
|
||||
if(!attacker.worldObj.isRemote){
|
||||
|
||||
// New way of choosing a spell; keeps trying until one works or all have been tried
|
||||
|
||||
Spell spell;
|
||||
|
||||
while(!spells.isEmpty()){
|
||||
|
||||
spell = spells.get(attacker.worldObj.rand.nextInt(spells.size()));
|
||||
|
||||
SpellModifiers modifiers = caster.getModifiers();
|
||||
|
||||
if(spell != null && spell.cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){
|
||||
|
||||
if(spell.isContinuous){
|
||||
// -1 because the spell has been cast once already!
|
||||
this.continuousSpellTimer = this.continuousSpellDuration - 1;
|
||||
setContinuousSpellAndNotify(spell, modifiers);
|
||||
|
||||
}else{
|
||||
// For now, the cooldown is just added to the constant base cooldown. I think this
|
||||
// is a reasonable way of doing things; it's certainly better than before.
|
||||
this.cooldown = this.baseCooldown + spell.cooldown;
|
||||
|
||||
if(spell.doesSpellRequirePacket()){
|
||||
// Sends a packet to all players in dimension to tell them to spawn particles.
|
||||
IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers);
|
||||
WizardryPacketHandler.net.sendToDimension(msg, attacker.worldObj.provider.getDimension());
|
||||
}
|
||||
}
|
||||
|
||||
attacker.rotationYaw = (float)(Math.atan2(dz, dx) * 180.0D / Math.PI) - 90.0F;
|
||||
|
||||
return;
|
||||
|
||||
}else{
|
||||
spells.remove(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else if(this.cooldown < 0){
|
||||
// This should only be reached when the entity first starts attacking. Stops it attacking instantly.
|
||||
this.cooldown = this.baseCooldown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
|
||||
public class EntityAISelectSpell extends EntityAIBase {
|
||||
|
||||
// TODO: Write this class
|
||||
|
||||
@Override
|
||||
public boolean shouldExecute(){
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntityBlazeMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntityBlazeMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntityBlaze overrides
|
||||
|
||||
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
|
||||
// targeting system with one that targets hostile mobs and takes the ADS into account.
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
super.initEntityAI();
|
||||
this.targetTasks.taskEntries.clear();
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
/** Normally this would be private, but since this class has subclasses with different spawn/despawn particle
|
||||
* effects, it makes sense to have them override this rather than both onSpawn() and onDespawn(). */
|
||||
protected void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityDecoy extends EntitySummonedCreature {
|
||||
|
||||
public EntityDecoy(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityDecoy(World world, double x, double y, double z, EntityLivingBase caster, int lifetime) {
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.setAlwaysRenderNameTag(caster instanceof EntityPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI() {
|
||||
// Decoys just wander around aimlessly, watching anything living.
|
||||
this.tasks.addTask(0, new EntityAISwimming(this));
|
||||
this.tasks.addTask(1, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(2, new EntityAIWatchClosest(this, EntityLivingBase.class, 6.0F));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
super.onDespawn();
|
||||
for(int i=0; i<20; i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, worldObj, this.posX + (this.rand.nextDouble()-0.5)*this.width,
|
||||
this.getEntityBoundingBox().minY + this.rand.nextDouble()*this.height, this.posZ + (this.rand.nextDouble()-0.5)*this.width,
|
||||
0, 0, 0, 40, 0.2f, 1.0f, 0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntityInvulnerable(DamageSource source){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
super.onUpdate();
|
||||
if(this.getCaster() == null || this.getCaster().isDead){
|
||||
this.setDead();
|
||||
this.onDespawn();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes()
|
||||
{
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.25D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() instanceof EntityPlayer){
|
||||
return this.getCaster().getDisplayName();
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
return getCaster() instanceof EntityPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
super.writeSpawnData(data);
|
||||
if(this.getCaster() != null) data.writeInt(this.getCaster().getEntityId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
super.readSpawnData(data);
|
||||
if(!data.isReadable()) return;
|
||||
this.setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)this.worldObj.getEntityByID(data.readInt())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryAchievements;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAIRestrictOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest2;
|
||||
import net.minecraft.entity.monster.EntityMob;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagInt;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
import net.minecraft.pathfinding.PathNavigateGround;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntityAdditionalSpawnData {
|
||||
|
||||
private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell(this, 0.5D, 14.0F, 30, 50);
|
||||
|
||||
public int textureIndex = 0;
|
||||
|
||||
public boolean hasTower = false;
|
||||
|
||||
/** The entity selector passed into the new AI methods. */
|
||||
protected Predicate<Entity> targetSelector;
|
||||
|
||||
/** Data parameter for the cooldown time for wizards healing themselves. */
|
||||
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
|
||||
/** Data parameter for the wizard's element. */
|
||||
private static final DataParameter<Integer> ELEMENT = EntityDataManager.createKey(EntityEvilWizard.class, DataSerializers.VARINT);
|
||||
/** The resource location for the evil wizard's loot table. */
|
||||
private static final ResourceLocation LOOT_TABLE = new ResourceLocation(Wizardry.MODID, "entities/evil_wizard");
|
||||
|
||||
// Field implementations
|
||||
private List<Spell> spells = new ArrayList<Spell>(4);
|
||||
private Spell continuousSpell;
|
||||
|
||||
public EntityEvilWizard(World world){
|
||||
|
||||
super(world);
|
||||
this.setSize(0.6F, 1.8F);
|
||||
((PathNavigateGround)this.getNavigator()).setBreakDoors(true);
|
||||
|
||||
// For some reason this can't be done in initEntityAI
|
||||
this.tasks.addTask(3, this.spellCastingAI);
|
||||
|
||||
this.detachHome();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit(){
|
||||
super.entityInit();
|
||||
this.dataManager.register(HEAL_COOLDOWN, -1);
|
||||
this.dataManager.register(ELEMENT, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
this.tasks.addTask(0, new EntityAISwimming(this));
|
||||
this.tasks.addTask(4, new EntityAIRestrictOpenDoor(this));
|
||||
this.tasks.addTask(5, new EntityAIOpenDoor(this, true));
|
||||
this.tasks.addTask(6, new EntityAIMoveTowardsRestriction(this, 0.6D));
|
||||
this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 0.6D));
|
||||
|
||||
this.targetSelector = new Predicate<Entity>(){
|
||||
|
||||
public boolean apply(Entity entity){
|
||||
|
||||
// If the target is valid and not invisible...
|
||||
if(entity != null && !entity.isInvisible() && WizardryUtilities.isValidTarget(EntityEvilWizard.this, entity)){
|
||||
|
||||
//... and is a player, a summoned creature, another (non-evil) wizard ...
|
||||
if(entity instanceof EntityPlayer || (entity instanceof ISummonedCreature || entity instanceof EntityWizard
|
||||
// ... or in the whitelist ...
|
||||
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT)))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.targetSelector));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes()
|
||||
{
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.5D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30);
|
||||
}
|
||||
|
||||
private int getHealCooldown(){
|
||||
return this.dataManager.get(HEAL_COOLDOWN);
|
||||
}
|
||||
|
||||
private void setHealCooldown(int cooldown){
|
||||
this.dataManager.set(HEAL_COOLDOWN, cooldown);
|
||||
}
|
||||
|
||||
public Element getElement(){
|
||||
return Element.values()[this.dataManager.get(ELEMENT)];
|
||||
}
|
||||
|
||||
public void setElement(Element element){
|
||||
this.dataManager.set(ELEMENT, element.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return this.spells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
this.continuousSpell = spell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return this.continuousSpell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
super.onLivingUpdate();
|
||||
|
||||
int healCooldown = this.getHealCooldown();
|
||||
|
||||
// This is now done slightly differently because isPotionActive doesn't work on client here, meaning that when
|
||||
// affected with arcane jammer and healCooldown == 0, whilst the wizard didn't actually heal or play the sound,
|
||||
// the particles still spawned, and since healCooldown wasn't reset they spawned every tick until the arcane
|
||||
// jammer wore off.
|
||||
if(healCooldown == 0 && this.getHealth() < this.getMaxHealth() && this.getHealth() > 0 && !this.isPotionActive(WizardryPotions.arcane_jammer)){
|
||||
|
||||
// Healer wizards use greater heal.
|
||||
this.heal(this.getElement() == Element.HEALING ? 8 : 4);
|
||||
this.setHealCooldown(-1);
|
||||
|
||||
// deathTime == 0 checks the wizard isn't currently dying
|
||||
}else if(healCooldown == -1 && this.deathTime == 0){
|
||||
|
||||
// Heal particles
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<10; i++){
|
||||
double d0 = (double)((float)this.posX + rand.nextFloat()*2 - 1.0F);
|
||||
// Apparently the client side spawns the particles 1 block higher than it should... hence the - 0.5F.
|
||||
double d1 = (double)((float)this.posY - 0.5F + rand.nextFloat());
|
||||
double d2 = (double)((float)this.posZ + rand.nextFloat()*2 - 1.0F);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, d0, d1, d2, 0, 0.1F, 0, 48 + rand.nextInt(12), 1.0f, 1.0f, 0.3f);
|
||||
}
|
||||
}else{
|
||||
if(this.getHealth() < 10){
|
||||
// Wizard heals himself more often if he has low health
|
||||
this.setHealCooldown(150);
|
||||
}else{
|
||||
this.setHealCooldown(400);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
}
|
||||
}
|
||||
if(healCooldown > 0){
|
||||
this.setHealCooldown(healCooldown - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack){
|
||||
|
||||
// Debugging
|
||||
//player.addChatComponentMessage(new TextComponentTranslation("wizard.debug", Spell.get(spells[1]).getDisplayName(), Spell.get(spells[2]).getDisplayName(), Spell.get(spells[3]).getDisplayName()));
|
||||
|
||||
// When right-clicked with a spell book in creative, sets one of the spells to that spell
|
||||
if(player.capabilities.isCreativeMode && stack != null && stack.getItem() instanceof ItemSpellBook){
|
||||
if(this.spells.size() >= 4 && Spell.get(stack.getItemDamage()).canBeCastByNPCs()){
|
||||
this.spells.set(rand.nextInt(3)+1, Spell.get(stack.getItemDamage()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbt){
|
||||
super.writeEntityToNBT(nbt);
|
||||
nbt.setInteger("element", this.getElement().ordinal());
|
||||
nbt.setInteger("skin", this.textureIndex);
|
||||
nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id())));
|
||||
nbt.setBoolean("hasTower", this.hasTower);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbt){
|
||||
super.readEntityFromNBT(nbt);
|
||||
this.setElement(Element.values()[nbt.getInteger("element")]);
|
||||
this.textureIndex = nbt.getInteger("skin");
|
||||
this.spells = (List<Spell>) WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
|
||||
(NBTTagInt tag) -> Spell.get(tag.getInt()));
|
||||
this.hasTower = nbt.getBoolean("hasTower");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canDespawn(){
|
||||
// Evil wizards can only despawn if they don't have a tower (i.e. if they spawned naturally at night)
|
||||
return !this.hasTower;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSoundPitch(){
|
||||
return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound() {
|
||||
return SoundEvents.ENTITY_WITCH_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound()
|
||||
{
|
||||
return SoundEvents.ENTITY_WITCH_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound()
|
||||
{
|
||||
return SoundEvents.ENTITY_WITCH_DEATH;
|
||||
}
|
||||
|
||||
// Although it *looks* like this is still called, in actual fact the only method that calls it is overridden in
|
||||
// EntityLiving to use the loot table system instead. This has been kept as a fallback in case the loot table is
|
||||
// not found.
|
||||
@Override
|
||||
protected void dropFewItems(boolean hitByPlayer, int lootingLevel){
|
||||
// Drops 3-5 crystals without looting bonuses
|
||||
int j = 3 + this.rand.nextInt(3) + this.rand.nextInt(1 + lootingLevel);
|
||||
|
||||
for(int k=0; k<j; k++){
|
||||
this.dropItem(WizardryItems.magic_crystal, 1);
|
||||
}
|
||||
|
||||
// Evil wizards occasionally drop one of their spells as a spell book, but not magic missile. This isn't in
|
||||
// the dropRareDrop method because that would be just as rare as normal mobs; instead this is half as rare.
|
||||
if(this.spells.size() > 0 && rand.nextInt(100) - lootingLevel < 5) this.entityDropItem(new ItemStack(WizardryItems.spell_book, 1, this.spells.get(1 + rand.nextInt(this.spells.size() - 1)).id()), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceLocation getLootTable(){
|
||||
return LOOT_TABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(DamageSource source){
|
||||
|
||||
super.onDeath(source);
|
||||
if(source.getEntity() instanceof EntityPlayer){
|
||||
((EntityPlayer)source.getEntity()).addStat(WizardryAchievements.defeat_evil_wizard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData data){
|
||||
|
||||
data = super.onInitialSpawn(difficulty, data);
|
||||
|
||||
textureIndex = this.rand.nextInt(6);
|
||||
|
||||
if(rand.nextBoolean()){
|
||||
this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]);
|
||||
}else{
|
||||
this.setElement(Element.MAGIC);
|
||||
}
|
||||
|
||||
Element element = this.getElement();
|
||||
|
||||
// Adds armour.
|
||||
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
|
||||
this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot)));
|
||||
}
|
||||
|
||||
// Default chance is 0.085f, for reference.
|
||||
for(EntityEquipmentSlot slot : EntityEquipmentSlot.values()) this.setDropChance(slot, 0.0f);
|
||||
|
||||
// All wizards know magic missile, even if it is disabled.
|
||||
spells.add(Spells.magic_missile);
|
||||
|
||||
Tier maxTier = EntityWizard.populateSpells(spells, element, 3, rand);
|
||||
|
||||
// Now done after the spells so it can take the tier into account. For evil wizards this is slightly different;
|
||||
// it picks a random wand which is at least a high enough tier for the spells the wizard has.
|
||||
Tier tier = Tier.values()[maxTier.ordinal() + rand.nextInt(Tier.values().length - maxTier.ordinal())];
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryUtilities.getWand(tier, element)));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
data.writeInt(textureIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
textureIndex = data.readInt();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.monster.EntityIronGolem;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.village.Village;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntityIceGiant(World world){
|
||||
super(world);
|
||||
this.setSize(1.4F, 2.9F);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntityIceGiant(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setSize(1.4F, 2.9F);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
this.getNavigator().getNodeProcessor().setCanSwim(false);
|
||||
this.tasks.addTask(1, new EntityAIAttackMelee(this, 1.0D, true));
|
||||
this.tasks.addTask(2, new EntityAIMoveTowardsTarget(this, 0.9D, 32.0F));
|
||||
//this.tasks.addTask(4, new EntityAIMoveTowardsRestriction(this, 1.0D));
|
||||
//this.tasks.addTask(5, new EntityAIWander(this, 0.6D));
|
||||
this.tasks.addTask(6, new EntityAIWatchClosest(this, EntityPlayer.class, 6.0F));
|
||||
this.tasks.addTask(7, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// EntityIronGolem overrides
|
||||
|
||||
@Override protected void updateAITasks(){} // Disables home-checking
|
||||
@Override public Village getVillage(){ return null; }
|
||||
@Override public int getHoldRoseTick(){ return 0; }
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f);
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0; i<30; i++){
|
||||
float brightness = 0.5f + (rand.nextFloat()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, this.worldObj, this.posX-1 + rand.nextDouble()*2, this.posY+ rand.nextDouble()*3, this.posZ-1 + rand.nextDouble()*2, 0, -0.02, 0, 12 + rand.nextInt(8), brightness, brightness + 0.1f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
super.onLivingUpdate();
|
||||
|
||||
if(this.worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, this.worldObj, this.posX-1 + rand.nextDouble()*2, this.posY+ rand.nextDouble()*3, this.posZ-1 + rand.nextDouble()*2, 0, -0.02, 0, 40 + rand.nextInt(10));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target){
|
||||
|
||||
target.motionY += 0.2;
|
||||
target.motionX += this.getLookVec().xCoord*0.2;
|
||||
target.motionZ += this.getLookVec().xCoord*0.2;
|
||||
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 0));
|
||||
|
||||
this.applyEnchantments(this, target);
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_IRONGOLEM_ATTACK, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom onDespawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceWraith extends EntityBlazeMinion {
|
||||
|
||||
/** The version from EntityLivingBase is only used in onLivingUpdate, so it can safely be copied. */
|
||||
private int jumpTicks;
|
||||
|
||||
public EntityIceWraith(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityIceWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.isImmuneToFire = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
super.initEntityAI();
|
||||
this.tasks.taskEntries.clear();
|
||||
this.tasks.addTask(4, new AIIceShardAttack(this));
|
||||
this.tasks.addTask(5, new EntityAIMoveTowardsRestriction(this, 1.0D));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(8, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
|
||||
this.tasks.addTask(8, new EntityAILookIdle(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnParticleEffect() {
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
float brightness = 0.5f + (rand.nextFloat()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - 0.5d + rand.nextDouble(), this.posY + this.height/2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, brightness + 0.1f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(!this.onGround && this.motionY < 0.0D){
|
||||
this.motionY *= 0.6D;
|
||||
}
|
||||
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 0.3F + this.rand.nextFloat()/4, this.rand.nextFloat() * 0.7F + 1.4F);
|
||||
}
|
||||
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i = 0; i < 2; ++i){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.CLOUD, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0D, 0.0D, 0.0D);
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces super call.
|
||||
this.livingBaseUpdate();
|
||||
}
|
||||
|
||||
/** Copied from {@link EntityLivingBase#onLivingUpdate()}. The only change is removal of the updateElytra() call
|
||||
* since that's irrelevant here. In actual fact, neither EntityMob nor EntityLiving has any code in its version
|
||||
* of this method that is of use. This isn't exactly ideal, but it's the lesser of two evils since the
|
||||
* alternative is copying the entire EntityBlaze class and its renderer. All to remove one particle effect... */
|
||||
// ... demonstrating why critical methods should delegate any non-critical functionality (like particles) to
|
||||
// separate protected methods.
|
||||
private void livingBaseUpdate(){
|
||||
|
||||
if (this.jumpTicks > 0)
|
||||
{
|
||||
--this.jumpTicks;
|
||||
}
|
||||
|
||||
if (this.newPosRotationIncrements > 0 && !this.canPassengerSteer())
|
||||
{
|
||||
double d0 = this.posX + (this.interpTargetX - this.posX) / (double)this.newPosRotationIncrements;
|
||||
double d1 = this.posY + (this.interpTargetY - this.posY) / (double)this.newPosRotationIncrements;
|
||||
double d2 = this.posZ + (this.interpTargetZ - this.posZ) / (double)this.newPosRotationIncrements;
|
||||
double d3 = MathHelper.wrapDegrees(this.interpTargetYaw - (double)this.rotationYaw);
|
||||
this.rotationYaw = (float)((double)this.rotationYaw + d3 / (double)this.newPosRotationIncrements);
|
||||
this.rotationPitch = (float)((double)this.rotationPitch + (this.interpTargetPitch - (double)this.rotationPitch) / (double)this.newPosRotationIncrements);
|
||||
--this.newPosRotationIncrements;
|
||||
this.setPosition(d0, d1, d2);
|
||||
this.setRotation(this.rotationYaw, this.rotationPitch);
|
||||
}
|
||||
else if (!this.isServerWorld())
|
||||
{
|
||||
this.motionX *= 0.98D;
|
||||
this.motionY *= 0.98D;
|
||||
this.motionZ *= 0.98D;
|
||||
}
|
||||
|
||||
if (Math.abs(this.motionX) < 0.003D)
|
||||
{
|
||||
this.motionX = 0.0D;
|
||||
}
|
||||
|
||||
if (Math.abs(this.motionY) < 0.003D)
|
||||
{
|
||||
this.motionY = 0.0D;
|
||||
}
|
||||
|
||||
if (Math.abs(this.motionZ) < 0.003D)
|
||||
{
|
||||
this.motionZ = 0.0D;
|
||||
}
|
||||
|
||||
this.worldObj.theProfiler.startSection("ai");
|
||||
|
||||
if (this.isMovementBlocked())
|
||||
{
|
||||
this.isJumping = false;
|
||||
this.moveStrafing = 0.0F;
|
||||
this.moveForward = 0.0F;
|
||||
this.randomYawVelocity = 0.0F;
|
||||
}
|
||||
else if (this.isServerWorld())
|
||||
{
|
||||
this.worldObj.theProfiler.startSection("newAi");
|
||||
this.updateEntityActionState();
|
||||
this.worldObj.theProfiler.endSection();
|
||||
}
|
||||
|
||||
this.worldObj.theProfiler.endSection();
|
||||
this.worldObj.theProfiler.startSection("jump");
|
||||
|
||||
if (this.isJumping)
|
||||
{
|
||||
if (this.isInWater())
|
||||
{
|
||||
this.handleJumpWater();
|
||||
}
|
||||
else if (this.isInLava())
|
||||
{
|
||||
this.handleJumpLava();
|
||||
}
|
||||
else if (this.onGround && this.jumpTicks == 0)
|
||||
{
|
||||
this.jump();
|
||||
this.jumpTicks = 10;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.jumpTicks = 0;
|
||||
}
|
||||
|
||||
this.worldObj.theProfiler.endSection();
|
||||
this.worldObj.theProfiler.startSection("travel");
|
||||
this.moveStrafing *= 0.98F;
|
||||
this.moveForward *= 0.98F;
|
||||
this.randomYawVelocity *= 0.9F;
|
||||
this.moveEntityWithHeading(this.moveStrafing, this.moveForward);
|
||||
this.worldObj.theProfiler.endSection();
|
||||
this.worldObj.theProfiler.startSection("push");
|
||||
this.collideWithNearbyEntities();
|
||||
this.worldObj.theProfiler.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float amount){
|
||||
// Removes the damage from being wet that applies to blazes by checking if the mob is actually drowning.
|
||||
if(source == DamageSource.drown && (this.getAir() > 0 || this.isPotionActive(MobEffects.WATER_BREATHING))){
|
||||
// In this case, the ice wraith is not actually drowning, so cancel the damage.
|
||||
return false;
|
||||
}else{
|
||||
return super.attackEntityFrom(source, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurning(){
|
||||
// Uses the datawatcher on both sides because fire is private to Entity (and I'm not using reflection here).
|
||||
// TESTME: This should work, but there may be some issues with updating, so if it doesn't work, copy the
|
||||
// version from Entity and use reflection to access the fire field.
|
||||
return this.getFlag(0);
|
||||
}
|
||||
|
||||
/** Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball
|
||||
* spawning with a one-liner call to WizardryRegistry.iceShard.cast(...) and the removal of redundant local variables. */
|
||||
static class AIIceShardAttack extends EntityAIBase {
|
||||
|
||||
private final EntityBlaze blaze;
|
||||
private int attackStep;
|
||||
private int attackTime;
|
||||
|
||||
public AIIceShardAttack(EntityBlaze blazeIn)
|
||||
{
|
||||
this.blaze = blazeIn;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the EntityAIBase should begin execution.
|
||||
*/
|
||||
public boolean shouldExecute()
|
||||
{
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
return entitylivingbase != null && entitylivingbase.isEntityAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a one shot task or start executing a continuous task
|
||||
*/
|
||||
public void startExecuting()
|
||||
{
|
||||
this.attackStep = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the task
|
||||
*/
|
||||
public void resetTask()
|
||||
{
|
||||
// This might be called setOnFire, but what it really controls is whether the wraith is in attack mode.
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the task
|
||||
*/
|
||||
public void updateTask()
|
||||
{
|
||||
--this.attackTime;
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
double d0 = this.blaze.getDistanceSqToEntity(entitylivingbase);
|
||||
|
||||
if (d0 < 4.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
this.attackTime = 20;
|
||||
this.blaze.attackEntityAsMob(entitylivingbase);
|
||||
}
|
||||
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
else if (d0 < 256.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
++this.attackStep;
|
||||
|
||||
if (this.attackStep == 1)
|
||||
{
|
||||
this.attackTime = 60;
|
||||
this.blaze.setOnFire(true);
|
||||
}
|
||||
else if (this.attackStep <= 4)
|
||||
{
|
||||
this.attackTime = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.attackTime = 100;
|
||||
this.attackStep = 0;
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
if(this.attackStep > 1){
|
||||
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
|
||||
Spells.ice_shard.cast(this.blaze.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
|
||||
}
|
||||
}
|
||||
|
||||
this.blaze.getLookHelper().setLookPositionWithEntity(entitylivingbase, 10.0F, 10.0F);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.blaze.getNavigator().clearPathEntity();
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
|
||||
super.updateTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityLightningWraith extends EntityBlazeMinion {
|
||||
|
||||
public EntityLightningWraith(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityLightningWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.isImmuneToFire = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
super.initEntityAI();
|
||||
this.tasks.taskEntries.clear();
|
||||
this.tasks.addTask(4, new AILightningAttack(this));
|
||||
this.tasks.addTask(5, new EntityAIMoveTowardsRestriction(this, 1.0D));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(8, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
|
||||
this.tasks.addTask(8, new EntityAILookIdle(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
float brightness = 0.3f + (rand.nextFloat()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - 0.5d + rand.nextDouble(), this.posY + this.height/2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, brightness + 0.2f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
// Fortunately, lightning wraiths don't replace any of blazes' particle effects or the fire sound, they only
|
||||
// add the sparks, so it's fine to call super here.
|
||||
if(worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0, 3);
|
||||
}
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float amount){
|
||||
// Removes the damage from being wet that applies to blazes by checking if the mob is actually drowning.
|
||||
if(source == DamageSource.drown && (this.getAir() > 0 || this.isPotionActive(MobEffects.WATER_BREATHING))){
|
||||
// In this case, the lightning wraith is not actually drowning, so cancel the damage.
|
||||
return false;
|
||||
}else{
|
||||
return super.attackEntityFrom(source, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurning(){
|
||||
// Uses the datawatcher on both sides because fire is private to Entity (and I'm not using reflection here).
|
||||
// TESTME: This should work, but there may be some issues with updating, so if it doesn't work, copy the
|
||||
// version from Entity and use reflection to access the fire field.
|
||||
return this.getFlag(0);
|
||||
}
|
||||
|
||||
/** Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball
|
||||
* spawning with a one-liner call to WizardryRegistry.arc.cast(...) and the removal of redundant local variables. */
|
||||
static class AILightningAttack extends EntityAIBase {
|
||||
|
||||
private final EntityBlaze blaze;
|
||||
private int attackStep;
|
||||
private int attackTime;
|
||||
|
||||
public AILightningAttack(EntityBlaze blazeIn)
|
||||
{
|
||||
this.blaze = blazeIn;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the EntityAIBase should begin execution.
|
||||
*/
|
||||
public boolean shouldExecute()
|
||||
{
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
return entitylivingbase != null && entitylivingbase.isEntityAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a one shot task or start executing a continuous task
|
||||
*/
|
||||
public void startExecuting()
|
||||
{
|
||||
this.attackStep = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the task
|
||||
*/
|
||||
public void resetTask()
|
||||
{
|
||||
// This might be called setOnFire, but what it really controls is whether the wraith is in attack mode.
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the task
|
||||
*/
|
||||
public void updateTask()
|
||||
{
|
||||
--this.attackTime;
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
double d0 = this.blaze.getDistanceSqToEntity(entitylivingbase);
|
||||
|
||||
if (d0 < 4.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
this.attackTime = 20;
|
||||
this.blaze.attackEntityAsMob(entitylivingbase);
|
||||
}
|
||||
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
else if (d0 < 256.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
++this.attackStep;
|
||||
|
||||
if (this.attackStep == 1)
|
||||
{
|
||||
this.attackTime = 60;
|
||||
this.blaze.setOnFire(true);
|
||||
}
|
||||
else if (this.attackStep <= 4)
|
||||
{
|
||||
this.attackTime = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.attackTime = 100;
|
||||
this.attackStep = 0;
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
if(this.attackStep > 1){
|
||||
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
|
||||
Spells.arc.cast(this.blaze.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
|
||||
}
|
||||
}
|
||||
|
||||
this.blaze.getLookHelper().setLookPositionWithEntity(entitylivingbase, 10.0F, 10.0F);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.blaze.getNavigator().clearPathEntity();
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
|
||||
super.updateTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.monster.EntitySlime;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** As of Wizardry 1.2, this is now an ISummonedCreature like the rest of them, and it extends EntitySlime. */
|
||||
public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 200;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
public EntityMagicSlime(World world){
|
||||
super(world);
|
||||
this.setSlimeSize(2); // Needs to be called before setting the experience value to 0
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new magic slime with the given caster and lifetime, riding the given target.
|
||||
* @param world The world that the slime is in.
|
||||
* @param caster The entity that created the slime.
|
||||
* @param target The slime's victim. The slime will automatically start riding this entity.
|
||||
* @param lifetime The number of ticks before the slime bursts.
|
||||
*/
|
||||
public EntityMagicSlime(World world, EntityLivingBase caster, EntityLivingBase target, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(target.posX, target.posY, target.posZ);
|
||||
this.startRiding(target);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.setSlimeSize(2); // Needs to be called before setting the experience value to 0
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySlime overrides
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){} // Has no AI!
|
||||
|
||||
@Override
|
||||
protected void dealDamage(EntityLivingBase entity){} // Handles damage itself
|
||||
|
||||
@Override
|
||||
public void setDead(){
|
||||
// Restores behaviour from Entity, replacing slime splitting behaviour.
|
||||
this.isDead = true;
|
||||
// Makes sure that the undoing in onUpdate won't undo this. For some reason, EntitySlime sets isDead directly
|
||||
// to do the peaceful despawning, which seems odd but is actually rather handy!
|
||||
this.setHealth(0);
|
||||
// Bursting effect
|
||||
for(int i=0; i<30; i++){
|
||||
double x = this.posX - 0.5 + rand.nextDouble();
|
||||
double y = this.posY - 0.5 + rand.nextDouble();
|
||||
double z = this.posZ - 0.5 + rand.nextDouble();
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.SLIME, x, y, z, (x - this.posX)*2, (y - this.posY)*2, (z - this.posZ)*2);
|
||||
}
|
||||
this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 2.5f, 0.6f);
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST_FAR, 1.0f, 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){
|
||||
// Removes size randomisation
|
||||
IEntityLivingData data = super.onInitialSpawn(difficulty, livingdata);
|
||||
this.setSlimeSize(2);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float amount){
|
||||
// Immune to suffocation
|
||||
return source == DamageSource.inWall ? false : super.attackEntityFrom(source, amount);
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
// Undoes the despawning on peaceful behaviour. I don't think there's anything in super.onUpdate that sets
|
||||
// isDead other than that, but it's better to do a quick sanity check just to be sure.
|
||||
if(this.isDead && worldObj.getDifficulty() == EnumDifficulty.PEACEFUL && this.getHealth() > 0) this.isDead = false;
|
||||
// Bursts instantly rather than doing the falling over animation.
|
||||
if(this.getHealth() <= 0) this.setDead();
|
||||
|
||||
this.updateDelegate();
|
||||
|
||||
// Damages and slows the slime's victim or makes the slime explode if the victim is dead.
|
||||
if(this.getRidingEntity() != null && this.getRidingEntity() instanceof EntityLivingBase && ((EntityLivingBase)this.getRidingEntity()).getHealth() > 0){
|
||||
if(this.ticksExisted % 16 == 1){
|
||||
this.getRidingEntity().attackEntityFrom(DamageSource.magic, 1);
|
||||
((EntityLivingBase)this.getRidingEntity()).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 20, 2));
|
||||
this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 1.0f, 1.0f);
|
||||
this.squishAmount = 0.5F;
|
||||
}
|
||||
}else{
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom onDespawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
private double AISpeed = 0.5;
|
||||
|
||||
// Can attack for 7 seconds, then must cool down for 3.
|
||||
private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 60, 140);
|
||||
|
||||
private Spell continuousSpell;
|
||||
|
||||
private static final List<Spell> attack = Collections.singletonList(Spells.flame_ray);
|
||||
|
||||
public EntityPhoenix(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityPhoenix(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.isImmuneToFire = true;
|
||||
this.height = 2.0f;
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(1, this.spellAttackAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(0, new EntityAIWatchClosest(this, EntityLivingBase.class, 0));
|
||||
//this.tasks.addTask(2, new EntityAIWander(this, AISpeed));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
//this.targetTasks.addTask(0, new EntityAIMoveTowardsTarget(this, 1, 10));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
|
||||
this.setAIMoveSpeed((float)AISpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return attack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return continuousSpell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
continuousSpell = spell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
// Makes the flames come from the phoenix's head rather than its body
|
||||
public float getEyeHeight(){
|
||||
return 2.1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getAttributeMap().registerAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(30.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float par1){
|
||||
return 15728880;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float par1){
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
// Makes the phoenix hover.
|
||||
int floorLevel = WizardryUtilities.getNearestFloorLevel(worldObj, new BlockPos(this), 4);
|
||||
|
||||
if(this.posY - floorLevel > 3){
|
||||
this.motionY = -0.1;
|
||||
}else if(this.posY - floorLevel < 2){
|
||||
this.motionY = 0.1;
|
||||
}else{
|
||||
this.motionY = 0.0;
|
||||
}
|
||||
|
||||
// Living sound
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
// Flapping sound effect
|
||||
if(this.ticksExisted % 22 == 0){
|
||||
this.playSound(SoundEvents.ENTITY_ENDERDRAGON_FLAP, 1.0F, 1.0f);
|
||||
}
|
||||
|
||||
for(int i=0; i<2; i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.height/2 + this.rand.nextDouble() * (double)this.height/2, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0D, -0.1D, 0.0D);
|
||||
}
|
||||
|
||||
// Adding this allows the phoenix to attack despite being in the air. However, for some strange reason
|
||||
// it will only attack when within about 3 blocks of the ground. Any higher and it just sits there, not even
|
||||
// attempting to find targets.
|
||||
|
||||
this.onGround = true;
|
||||
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier){} // Immune to fall damage
|
||||
|
||||
@Override
|
||||
public boolean isBurning()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class EntityShadowWraith extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
// TODO: This currently doesn't fly like it used to. Should it, or does it not matter?
|
||||
|
||||
private double AISpeed = 1.0;
|
||||
|
||||
private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0);
|
||||
|
||||
private static final List<Spell> attack = Collections.singletonList(Spells.darkness_orb);
|
||||
|
||||
public EntityShadowWraith(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityShadowWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(0, this.spellAttackAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(1, new EntityAIAttackMelee(this, AISpeed, false));
|
||||
this.tasks.addTask(2, new EntityAIWander(this, AISpeed));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
|
||||
this.setAIMoveSpeed((float)AISpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return attack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return Spells.none;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
// Doesn't use continuous spells.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getAttributeMap().registerAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(AISpeed);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(16.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPotionApplicable(PotionEffect potion){
|
||||
return potion.getPotion() == MobEffects.WITHER ? false : super.isPotionApplicable(potion);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float partialTicks){
|
||||
return 15728880;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks){
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
float brightness = rand.nextFloat()*0.4f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - 0.5d + rand.nextDouble(), this.posY + this.height/2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, 0.0f, brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
// Slow fall
|
||||
if(!this.onGround && this.motionY < 0.0D){
|
||||
this.motionY *= 0.6D;
|
||||
}
|
||||
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<2; i++){
|
||||
worldObj.spawnParticle(EnumParticleTypes.PORTAL, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0);
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0);
|
||||
float brightness = rand.nextFloat()*0.2f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, 0.0f, brightness);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier){
|
||||
// Immune to fall damage.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.monster.EntitySilverfish;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySilverfishMinion extends EntitySilverfish implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySilverfishMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySilverfishMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySilverfish overrides
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
// Super not called because we don't want AISummonSilverfish or AIHideInStone
|
||||
this.tasks.addTask(1, new EntityAISwimming(this));
|
||||
this.tasks.addTask(4, new EntityAIAttackMelee(this, 1.0D, false));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0.0d, 0.0d, 0.0d, 0, 0.3f, 0.3f, 0.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target){
|
||||
if(!target.isEntityAlive()){
|
||||
this.onKillEntity(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKillEntity(EntityLivingBase victim) {
|
||||
// If the silverfish has a summoner, this is actually called from Wizardry's event handler rather than by
|
||||
// Minecraft itself, because the damagesource being changed causes it not to get called.
|
||||
if(!this.worldObj.isRemote){
|
||||
// Summons 1-4 more silverfish
|
||||
int alliesToSummon = rand.nextInt(4) + 1;
|
||||
|
||||
for(int i=0; i<alliesToSummon; i++){
|
||||
EntitySilverfishMinion silverfish = new EntitySilverfishMinion(this.worldObj, victim.posX, victim.posY, victim.posZ, this.getCaster(), this.lifetime);
|
||||
this.worldObj.spawnEntityInWorld(silverfish);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.attributes.AttributeModifier;
|
||||
import net.minecraft.entity.monster.EntitySkeleton;
|
||||
import net.minecraft.entity.monster.SkeletonType;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySkeletonMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySkeletonMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySkeleton overrides
|
||||
|
||||
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
|
||||
// targeting system with one that targets hostile mobs and takes the ADS into account.
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
super.initEntityAI();
|
||||
this.targetTasks.taskEntries.clear();
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// Shouldn't have randomised armour, but does still need a bow!
|
||||
@Override
|
||||
protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty) {
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW));
|
||||
}
|
||||
|
||||
// Where the skeleton minion is summoned does not affect its type.
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata)
|
||||
{
|
||||
// Can't call super, so the code from the next level up (EntityLiving) had to be copied as well.
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1));
|
||||
|
||||
if (this.rand.nextFloat() < 0.05F)
|
||||
{
|
||||
this.setLeftHanded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setLeftHanded(false);
|
||||
}
|
||||
|
||||
// Halloween pumpkin heads! Why not?
|
||||
if (this.getItemStackFromSlot(EntityEquipmentSlot.HEAD) == null)
|
||||
{
|
||||
Calendar calendar = this.worldObj.getCurrentDate();
|
||||
|
||||
if (calendar.get(2) + 1 == 10 && calendar.get(5) == 31 && this.rand.nextFloat() < 0.25F)
|
||||
{
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.HEAD, new ItemStack(this.rand.nextFloat() < 0.1F ? Blocks.LIT_PUMPKIN : Blocks.PUMPKIN));
|
||||
this.inventoryArmorDropChances[EntityEquipmentSlot.HEAD.getIndex()] = 0.0F;
|
||||
}
|
||||
}
|
||||
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target) {
|
||||
if(this.getSkeletonType() == SkeletonType.WITHER){
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.WITHER, 200));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.attributes.AttributeModifier;
|
||||
import net.minecraft.entity.monster.EntityCaveSpider;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySpiderMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySpiderMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySpider overrides
|
||||
|
||||
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
|
||||
// targeting system with one that targets hostile mobs and takes the ADS into account.
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
super.initEntityAI();
|
||||
this.targetTasks.taskEntries.clear();
|
||||
// Spiders use a custom AI type specific to spiders which I can't access, but it's just an extension of
|
||||
// EntityAINearestAttackableTarget which takes daylight into account. Since I want spider minions to attack
|
||||
// regardless of daylight, I can just use EntityAINearestAttackableTarget.
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// No spider jockeys!
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata) {
|
||||
|
||||
// Can't call super, so the code from the next level up (EntityLiving) had to be copied as well.
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1));
|
||||
|
||||
if (this.rand.nextFloat() < 0.05F)
|
||||
{
|
||||
this.setLeftHanded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setLeftHanded(false);
|
||||
}
|
||||
|
||||
// Don't need anything from EntitySpider, since neither spider jockeys nor group data is relevant.
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.2f, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target){
|
||||
|
||||
int seconds = 0;
|
||||
|
||||
if(this.worldObj.getDifficulty() == EnumDifficulty.NORMAL){
|
||||
seconds = 7;
|
||||
}else if(this.worldObj.getDifficulty() == EnumDifficulty.HARD){
|
||||
seconds = 15;
|
||||
}
|
||||
|
||||
if(seconds > 0){
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.POISON, seconds * 20, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.passive.EntityAnimal;
|
||||
import net.minecraft.entity.passive.EntityHorse;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.util.text.translation.I18n;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** Does not implement ISummonedCreature because it has different despawning rules and because EntityHorse already
|
||||
* has an owner system. */
|
||||
@SuppressWarnings("deprecation") // It's what Entity does, so...
|
||||
public class EntitySpiritHorse extends EntityHorse {
|
||||
|
||||
private int idleTimer = 0;
|
||||
|
||||
public EntitySpiritHorse(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
if (this.hasCustomName())
|
||||
{
|
||||
return this.getCustomNameTag();
|
||||
}
|
||||
else
|
||||
{
|
||||
return I18n.translateToLocal("entity.wizardry.Spirit Horse.name");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChested()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalArmorValue()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getExperiencePoints(EntityPlayer p_70693_1_){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Item getDropItem(){
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dropFewItems(boolean par1, int par2){}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes()
|
||||
{
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(24.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openGUI(EntityPlayer p_110199_1_){}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack){
|
||||
|
||||
ItemStack itemstack = player.inventory.getCurrentItem();
|
||||
|
||||
// Allows the owner (but not other players) to dispel the spirit horse using a wand (shift-clicking, because clicking mounts the horse in this case).
|
||||
if(itemstack != null && itemstack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
|
||||
// Prevents accidental double clicking.
|
||||
if(this.ticksExisted > 20){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
this.setDead();
|
||||
if(WizardData.get(player) != null){
|
||||
WizardData.get(player).hasSpiritHorse = false;
|
||||
}
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
// This is necessary to prevent the wand's spell being cast when performing this action.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.processInteract(player, hand, itemstack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(DamageSource par1DamageSource){
|
||||
|
||||
super.onDeath(par1DamageSource);
|
||||
|
||||
// Allows player to summon another spirit horse once this one has died.
|
||||
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
|
||||
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false;
|
||||
}
|
||||
}
|
||||
|
||||
// I wrote this one!
|
||||
private EntityLivingBase getOwner(){
|
||||
|
||||
// I think the DataManager stores any objects, so it now stores the UUID instead of its string representation.
|
||||
Entity owner = WizardryUtilities.getEntityByUUID(worldObj, this.getOwnerUniqueId());
|
||||
|
||||
if(owner instanceof EntityLivingBase){
|
||||
return (EntityLivingBase)owner;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Adds a dust particle effect
|
||||
if(this.worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 0, 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
|
||||
// Spirit horse disappears a short time after being dismounted.
|
||||
if(!this.isBeingRidden()){
|
||||
this.idleTimer++;
|
||||
}else if(this.idleTimer > 0){
|
||||
this.idleTimer = 0;
|
||||
}
|
||||
|
||||
if(this.idleTimer > 200){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
// Allows player to summon another spirit horse once this one has disappeared.
|
||||
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
|
||||
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false;
|
||||
}
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMateWith(EntityAnimal par1EntityAnimal){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData data){
|
||||
|
||||
// Adds Particles on spawn. Due to client/server differences this cannot be done in the item.
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return super.onInitialSpawn(difficulty, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getOwner() != null){
|
||||
return new TextComponentTranslation(ISummonedCreature.NAMEPLATE_TRANSLATION_KEY, getOwner().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getOwner() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityAgeable;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIFollowOwner;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILeapAtTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIOwnerHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAIOwnerHurtTarget;
|
||||
import net.minecraft.entity.ai.EntityAISit;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.passive.EntityWolf;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** Does not implement ISummonedCreature because it has different despawning rules and because EntityWolf already
|
||||
* has an owner system. */
|
||||
public class EntitySpiritWolf extends EntityWolf {
|
||||
|
||||
public EntitySpiritWolf(World par1World){
|
||||
|
||||
super(par1World);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.aiSit = new EntityAISit(this);
|
||||
this.tasks.addTask(1, new EntityAISwimming(this));
|
||||
this.tasks.addTask(2, this.aiSit);
|
||||
this.tasks.addTask(3, new EntityAILeapAtTarget(this, 0.4F));
|
||||
this.tasks.addTask(4, new EntityAIAttackMelee(this, 1.0D, true));
|
||||
this.tasks.addTask(5, new EntityAIFollowOwner(this, 1.0D, 10.0F, 2.0F));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(9, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
|
||||
this.tasks.addTask(9, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIOwnerHurtByTarget(this));
|
||||
this.targetTasks.addTask(2, new EntityAIOwnerHurtTarget(this));
|
||||
this.targetTasks.addTask(3, new EntityAIHurtByTarget(this, true, new Class[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(DamageSource source){
|
||||
|
||||
// Allows player to summon another spirit wolf once this one has died.
|
||||
// NOTE: This has been known to work incorrectly.
|
||||
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
|
||||
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritWolf = false;
|
||||
}
|
||||
|
||||
super.onDeath(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getExperiencePoints(EntityPlayer p_70693_1_){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata) {
|
||||
|
||||
// Adds Particles on spawn. Due to client/server differences this cannot be done in the item.
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Adds a dust particle effect
|
||||
if(this.worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 0, 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
|
||||
if (this.isTamed())
|
||||
{
|
||||
if (stack != null){
|
||||
|
||||
// Allows the owner (but not other players) to dispel the spirit wolf using a wand.
|
||||
if(stack != null && stack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
|
||||
// Prevents accidental double clicking.
|
||||
if(this.ticksExisted > 20){
|
||||
for(int i=0;i<10;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
this.setDead();
|
||||
if(WizardData.get(player) != null){
|
||||
WizardData.get(player).hasSpiritWolf = false;
|
||||
}
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
// This is necessary to prevent the wand's spell being cast when performing this action.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityWolf createChild(EntityAgeable par1EntityAgeable)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Item getDropItem()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getOwner() != null){
|
||||
return new TextComponentTranslation(ISummonedCreature.NAMEPLATE_TRANSLATION_KEY, getOwner().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getOwner() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.effect.EntityLightningBolt;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class EntityStormElemental extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
private double AISpeed = 1.0;
|
||||
|
||||
private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0);
|
||||
|
||||
private static final List<Spell> attack = Collections.singletonList(Spells.lightning_disc);
|
||||
|
||||
public EntityStormElemental(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityStormElemental(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(0, this.spellAttackAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(1, new EntityAIAttackMelee(this, AISpeed, false));
|
||||
this.tasks.addTask(2, new EntityAIWander(this, AISpeed));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
|
||||
this.setAIMoveSpeed((float)AISpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return attack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return Spells.none;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
// Doesn't use continuous spells.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getAttributeMap().registerAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(AISpeed);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(16.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float partialTicks){
|
||||
return 15728880;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks){
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(this.ticksExisted % 120 == 1){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
if (this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.ENTITY_BLAZE_BURN, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
// Slow fall
|
||||
if(!this.onGround && this.motionY < 0.0D){
|
||||
this.motionY *= 0.6D;
|
||||
}
|
||||
|
||||
if(worldObj.isRemote){
|
||||
|
||||
for(int i=0; i<2; ++i){
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
for(int i=0; i<10; i++){
|
||||
float brightness = rand.nextFloat()*0.2f;
|
||||
double dy = this.rand.nextDouble() * (double)this.height;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE_ROTATING, worldObj, this.posX, this.posY + dy, this.posZ, 0, 0, 0, 20 + rand.nextInt(10), 0, brightness, brightness, false, 0.2f + 0.5f*dy);
|
||||
}
|
||||
}
|
||||
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier){
|
||||
// Immune to fall damage.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStruckByLightning(EntityLightningBolt lightning){
|
||||
// Immune to lightning.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityCreature;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** Abstract base implementation of {@link ISummonedCreature} which is the superclass to all custom summoned entities
|
||||
* (i.e. entities that don't extend vanilla/mod creatures). Also serves as an example of how to correctly implement
|
||||
* the above interface, and includes some non-critical method overrides which should be used for best results (xp, drops,
|
||||
* and such like). <i>Not to be confused with the old version of EntitySummonedCreature; that system has been replaced.</i>
|
||||
* @since Wizardry 1.2
|
||||
* @author Electroblob */
|
||||
public abstract class EntitySummonedCreature extends EntityCreature implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySummonedCreature(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySummonedCreature(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom onDespawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity and this entity only has melee attacks.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType) || this.hasRangedAttack();
|
||||
}
|
||||
|
||||
// TODO: Backport the following two methods to 1.7.10.
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
// Specific to EntitySummonedCreature, remove if copying
|
||||
|
||||
/** Whether this summoned creature has a ranged attack. Used to test whether it should attack flying creatures. */
|
||||
public abstract boolean hasRangedAttack();
|
||||
}
|
||||
@@ -0,0 +1,803 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryAchievements;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WandHelper;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityAgeable;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookAtTradePlayer;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAIRestrictOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAITradePlayer;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest2;
|
||||
import net.minecraft.entity.effect.EntityLightningBolt;
|
||||
import net.minecraft.entity.monster.IMob;
|
||||
import net.minecraft.entity.passive.EntityVillager;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagInt;
|
||||
import net.minecraft.nbt.NBTTagLong;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.village.MerchantRecipe;
|
||||
import net.minecraft.village.MerchantRecipeList;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
public class EntityWizard extends EntityVillager implements ISpellCaster, IEntityAdditionalSpawnData {
|
||||
|
||||
/*
|
||||
* After much debugging, the error in the compiled mod (outside of eclipse) was traced back to this class,
|
||||
* specifically the methods copied in from EntityVillager when I changed this class to extend it. This figures,
|
||||
* since I had 1.2.1 working just fine before I did that, and it was the only thing I changed. Apparently,
|
||||
* methods and fields with obfuscated names like func_129090_a can cause problems when compiled. One of the
|
||||
* ones here was renamed and the other deleted since it was never called. Watch out for this in future (unless,
|
||||
* of course, they are overriding something, in which case it should be fine).
|
||||
*/
|
||||
|
||||
// Extending EntityVillager turned out to be a pretty neat thing to do, since now zombies will attack wizards
|
||||
|
||||
private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell(this, 0.5D, 14.0F, 30, 50);
|
||||
|
||||
public int textureIndex = 0;
|
||||
|
||||
/** The entity selector passed into the new AI methods. */
|
||||
protected Predicate<Entity> targetSelector;
|
||||
|
||||
/** Copy of EntityVillager's buyingList, renamed to avoid confusion. */
|
||||
private MerchantRecipeList trades;
|
||||
private int timeUntilReset;
|
||||
|
||||
/** addDefaultEquipmentAndRecipies is called if this is true */
|
||||
private boolean updateRecipes;
|
||||
|
||||
/** Data parameter for the cooldown time for wizards healing themselves. */
|
||||
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
|
||||
/** Data parameter for the wizard's element. */
|
||||
private static final DataParameter<Integer> ELEMENT = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
|
||||
|
||||
// Field implementations
|
||||
private List<Spell> spells = new ArrayList<Spell>(4);
|
||||
private Spell continuousSpell;
|
||||
|
||||
/** A set of the positions of the blocks that are part of this wizard's tower. */
|
||||
private Set<BlockPos> towerBlocks;
|
||||
|
||||
public EntityWizard(World world){
|
||||
super(world);
|
||||
this.detachHome();
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(3, this.spellCastingAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit(){
|
||||
super.entityInit();
|
||||
this.dataManager.register(HEAL_COOLDOWN, -1);
|
||||
this.dataManager.register(ELEMENT, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(0, new EntityAISwimming(this));
|
||||
this.tasks.addTask(1, new EntityAITradePlayer(this));
|
||||
this.tasks.addTask(1, new EntityAILookAtTradePlayer(this));
|
||||
this.tasks.addTask(4, new EntityAIRestrictOpenDoor(this));
|
||||
this.tasks.addTask(5, new EntityAIOpenDoor(this, true));
|
||||
this.tasks.addTask(6, new EntityAIMoveTowardsRestriction(this, 0.6D));
|
||||
this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
|
||||
this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityWizard.class, 5.0F, 0.02F));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 0.6D));
|
||||
this.tasks.addTask(8, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F));
|
||||
|
||||
this.targetSelector = new Predicate<Entity>(){
|
||||
|
||||
public boolean apply(Entity entity){
|
||||
|
||||
// If the target is valid and not invisible...
|
||||
if(entity != null && !entity.isInvisible() && WizardryUtilities.isValidTarget(EntityWizard.this, entity)){
|
||||
|
||||
//... and is a mob, a summoned creature ...
|
||||
if((entity instanceof IMob || entity instanceof ISummonedCreature
|
||||
// ... or in the whitelist ...
|
||||
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT)))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
// By default, wizards don't attack players unless the player has attacked them.
|
||||
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<EntityLiving>(this, EntityLiving.class, 0, false, true, this.targetSelector));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.5);
|
||||
}
|
||||
|
||||
private int getHealCooldown(){
|
||||
return this.dataManager.get(HEAL_COOLDOWN);
|
||||
}
|
||||
|
||||
private void setHealCooldown(int cooldown){
|
||||
this.dataManager.set(HEAL_COOLDOWN, cooldown);
|
||||
}
|
||||
|
||||
public Element getElement(){
|
||||
return Element.values()[this.dataManager.get(ELEMENT)];
|
||||
}
|
||||
|
||||
public void setElement(Element element){
|
||||
this.dataManager.set(ELEMENT, element.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return this.spells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
this.continuousSpell = spell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return this.continuousSpell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
super.onLivingUpdate();
|
||||
|
||||
// Still better to store this to a local variable as it's almost certainly more efficient.
|
||||
int healCooldown = this.getHealCooldown();
|
||||
|
||||
// This is now done slightly differently because isPotionActive doesn't work on client here, meaning that when
|
||||
// affected with arcane jammer and healCooldown == 0, whilst the wizard didn't actually heal or play the sound,
|
||||
// the particles still spawned, and since healCooldown wasn't reset they spawned every tick until the arcane
|
||||
// jammer wore off.
|
||||
if(healCooldown == 0 && this.getHealth() < this.getMaxHealth() && this.getHealth() > 0 && !this.isPotionActive(WizardryPotions.arcane_jammer)){
|
||||
|
||||
// Healer wizards use greater heal.
|
||||
this.heal(this.getElement() == Element.HEALING ? 8 : 4);
|
||||
this.setHealCooldown(-1);
|
||||
|
||||
// deathTime == 0 checks the wizard isn't currently dying
|
||||
}else if(healCooldown == -1 && this.deathTime == 0){
|
||||
|
||||
// Heal particles
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<10; i++){
|
||||
double d0 = (double)((float)this.posX + rand.nextFloat()*2 - 1.0F);
|
||||
// Apparently the client side spawns the particles 1 block higher than it should... hence the - 0.5F.
|
||||
double d1 = (double)((float)this.posY - 0.5F + rand.nextFloat());
|
||||
double d2 = (double)((float)this.posZ + rand.nextFloat()*2 - 1.0F);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, d0, d1, d2, 0, 0.1F, 0, 48 + rand.nextInt(12), 1.0f, 1.0f, 0.3f);
|
||||
}
|
||||
}else{
|
||||
if(this.getHealth() < 10){
|
||||
// Wizard heals himself more often if he has low health
|
||||
this.setHealCooldown(150);
|
||||
}else{
|
||||
this.setHealCooldown(400);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
if(healCooldown > 0){
|
||||
this.setHealCooldown(healCooldown-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateAITasks(){
|
||||
|
||||
if(!this.isTrading() && this.timeUntilReset > 0){
|
||||
|
||||
--this.timeUntilReset;
|
||||
|
||||
if(this.timeUntilReset <= 0){
|
||||
|
||||
if(this.updateRecipes){
|
||||
|
||||
for(MerchantRecipe merchantrecipe : this.trades){
|
||||
|
||||
if(merchantrecipe.isRecipeDisabled()){
|
||||
// Increases the number of allowed uses of a disabled recipe by a random number.
|
||||
merchantrecipe.increaseMaxTradeUses(this.rand.nextInt(6) + this.rand.nextInt(6) + 2);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.trades.size() < 12){
|
||||
this.addRandomRecipes(1);
|
||||
}
|
||||
|
||||
this.updateRecipes = false;
|
||||
}
|
||||
|
||||
this.addPotionEffect(new PotionEffect(MobEffects.REGENERATION, 200, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Super call removed because EntityVillager's version does things I don't want and the next one up is
|
||||
// in EntityLivingBase and does nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
|
||||
// Debugging
|
||||
//player.addChatComponentMessage(new TextComponentTranslation("wizard.debug", Spell.get(spells[1]).getDisplayName(), Spell.get(spells[2]).getDisplayName(), Spell.get(spells[3]).getDisplayName()));
|
||||
|
||||
// When right-clicked with a spell book in creative, sets one of the spells to that spell
|
||||
if(player.capabilities.isCreativeMode && stack != null && stack.getItem() instanceof ItemSpellBook){
|
||||
if(this.spells.size() >= 4 && Spell.get(stack.getItemDamage()).canBeCastByNPCs()){
|
||||
this.spells.set(rand.nextInt(3)+1, Spell.get(stack.getItemDamage()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Won't trade with a player that has attacked them.
|
||||
if (this.isEntityAlive() && !this.isTrading() && !this.isChild() && !player.isSneaking() && this.getAttackTarget() != player)
|
||||
{
|
||||
if (!this.worldObj.isRemote)
|
||||
{
|
||||
this.setCustomer(player);
|
||||
player.displayVillagerTradeGui(this);
|
||||
//player.displayGUIMerchant(this, this.getElement().getWizardName());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName() {
|
||||
return this.getElement().getWizardName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbt){
|
||||
|
||||
super.writeEntityToNBT(nbt);
|
||||
|
||||
if (this.trades != null){
|
||||
nbt.setTag("trades", this.trades.getRecipiesAsTags());
|
||||
}
|
||||
|
||||
nbt.setInteger("element", this.getElement().ordinal());
|
||||
nbt.setInteger("skin", this.textureIndex);
|
||||
nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id())));
|
||||
|
||||
if(this.towerBlocks != null && this.towerBlocks.size() > 0){
|
||||
nbt.setTag("towerBlocks", WizardryUtilities.listToNBT(this.towerBlocks, pos -> new NBTTagLong(pos.toLong())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbt){
|
||||
|
||||
super.readEntityFromNBT(nbt);
|
||||
|
||||
if(nbt.hasKey("trades")){
|
||||
NBTTagCompound nbttagcompound1 = nbt.getCompoundTag("trades");
|
||||
this.trades = new MerchantRecipeList(nbttagcompound1);
|
||||
}
|
||||
|
||||
this.setElement(Element.values()[nbt.getInteger("element")]);
|
||||
this.textureIndex = nbt.getInteger("skin");
|
||||
this.spells = (List<Spell>) WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
|
||||
(NBTTagInt tag) -> Spell.get(tag.getInt()));
|
||||
|
||||
this.towerBlocks = new HashSet<BlockPos>(WizardryUtilities.NBTToList(nbt.getTagList("towerBlocks",
|
||||
NBT.TAG_LONG), (NBTTagLong tag) -> BlockPos.fromLong(tag.getLong())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canDespawn(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrading()
|
||||
{
|
||||
return this.getCustomer() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useRecipe(MerchantRecipe merchantrecipe){
|
||||
|
||||
merchantrecipe.incrementToolUses();
|
||||
this.livingSoundTime = -this.getTalkInterval();
|
||||
this.playSound(SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch());
|
||||
|
||||
// Achievements
|
||||
if (this.getCustomer() != null)
|
||||
{
|
||||
this.getCustomer().addStat(WizardryAchievements.wizard_trade);
|
||||
|
||||
if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook
|
||||
&& Spell.get(merchantrecipe.getItemToSell().getItemDamage()).tier == Tier.MASTER){
|
||||
this.getCustomer().addStat(WizardryAchievements.buy_master_spell);
|
||||
}
|
||||
}
|
||||
|
||||
// Changed to a 4 in 5 chance of unlocking a new recipe.
|
||||
if(this.rand.nextInt(5) > 0){
|
||||
this.timeUntilReset = 40;
|
||||
this.updateRecipes = true;
|
||||
|
||||
if (this.getCustomer() != null)
|
||||
{
|
||||
this.getCustomer().getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is called from the gui in order to display the recipes (no surprise there), and this is actually where
|
||||
// the initialisation is done, i.e. the trades don't actually exist until some player goes to trade with the
|
||||
// villager, at which point the first is added.
|
||||
@Override
|
||||
public MerchantRecipeList getRecipes(EntityPlayer par1EntityPlayer){
|
||||
|
||||
if(this.trades == null){
|
||||
|
||||
this.trades = new MerchantRecipeList();
|
||||
|
||||
// All wizards will buy spell books
|
||||
ItemStack anySpellBook = new ItemStack(WizardryItems.spell_book, 1, OreDictionary.WILDCARD_VALUE);
|
||||
ItemStack crystalStack = new ItemStack(WizardryItems.magic_crystal, 5);
|
||||
|
||||
// NOTE: For wizardry 1.2, increase the number of uses of this trade. The default is 7, for reference.
|
||||
this.trades.add(new MerchantRecipe(anySpellBook, crystalStack));
|
||||
|
||||
this.addRandomRecipes(3);
|
||||
}
|
||||
|
||||
return this.trades;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called once on initialisation and then once each time the wizard gains new trades (the particle thingy).
|
||||
*/
|
||||
private void addRandomRecipes(int numberOfItemsToAdd){
|
||||
|
||||
MerchantRecipeList merchantrecipelist;
|
||||
merchantrecipelist = new MerchantRecipeList();
|
||||
|
||||
for(int i=0; i<numberOfItemsToAdd; i++){
|
||||
|
||||
ItemStack itemToSell = null;
|
||||
|
||||
boolean itemAlreadySold = true;
|
||||
|
||||
Tier tier = Tier.BASIC;
|
||||
|
||||
while(itemAlreadySold){
|
||||
|
||||
itemAlreadySold = false;
|
||||
|
||||
/* New way of getting random item, by giving a chance to increase the tier which depends on how much the
|
||||
* player has already traded with the wizard. The more the player has traded with the wizard, the more
|
||||
* likely they are to get items of a higher tier. The -4 is to ignore the original 4 trades.
|
||||
* For reference, the chances are as follows:
|
||||
* Trades done Basic Apprentice Advanced Master
|
||||
* 0 50% 25% 18% 8%
|
||||
* 1 46% 25% 20% 9%
|
||||
* 2 42% 24% 22% 12%
|
||||
* 3 38% 24% 24% 14%
|
||||
* 4 34% 22% 26% 17%
|
||||
* 5 30% 21% 28% 21%
|
||||
* 6 26% 19% 30% 24%
|
||||
* 7 22% 17% 32% 28%
|
||||
* 8 18% 15% 34% 33% */
|
||||
|
||||
double tierIncreaseChance = 0.5 + 0.04*(Math.max(this.trades.size()-4, 0));
|
||||
|
||||
tier = Tier.BASIC;
|
||||
|
||||
if(rand.nextDouble() < tierIncreaseChance){
|
||||
tier = Tier.APPRENTICE;
|
||||
if(rand.nextDouble() < tierIncreaseChance){
|
||||
tier = Tier.ADVANCED;
|
||||
if(rand.nextDouble() < tierIncreaseChance*0.6){
|
||||
tier = Tier.MASTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemToSell = this.getRandomItemOfTier(tier);
|
||||
|
||||
for(Object recipe : merchantrecipelist){
|
||||
if(ItemStack.areItemStacksEqual(((MerchantRecipe)recipe).getItemToSell(), itemToSell)) itemAlreadySold = true;
|
||||
}
|
||||
|
||||
if(this.trades != null){
|
||||
for(Object recipe : this.trades){
|
||||
if(ItemStack.areItemStacksEqual(((MerchantRecipe)recipe).getItemToSell(), itemToSell)) itemAlreadySold = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't know how it can ever be null here, but saves it crashing.
|
||||
if(itemToSell == null) return;
|
||||
|
||||
merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier), new ItemStack(WizardryItems.magic_crystal, tier.ordinal()*3 + 1 + rand.nextInt(4)), itemToSell));
|
||||
}
|
||||
|
||||
Collections.shuffle(merchantrecipelist);
|
||||
|
||||
if (this.trades == null)
|
||||
{
|
||||
this.trades = new MerchantRecipeList();
|
||||
}
|
||||
|
||||
for (int j1 = 0; j1 < merchantrecipelist.size(); ++j1)
|
||||
{
|
||||
this.trades.add(merchantrecipelist.get(j1));
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack getRandomPrice(Tier tier) {
|
||||
ItemStack itemstack = null;
|
||||
switch(this.rand.nextInt(3)){
|
||||
case 0:
|
||||
itemstack = new ItemStack(Items.GOLD_INGOT, (tier.ordinal()+1)*8-1 + rand.nextInt(6));
|
||||
break;
|
||||
case 1:
|
||||
itemstack = new ItemStack(Items.DIAMOND, (tier.ordinal()+1)*4-2 + rand.nextInt(3));
|
||||
break;
|
||||
case 2:
|
||||
itemstack = new ItemStack(Items.EMERALD, (tier.ordinal()+1)*6-1 + rand.nextInt(3));
|
||||
break;
|
||||
}
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
private ItemStack getRandomItemOfTier(Tier tier){
|
||||
|
||||
int randomiser;
|
||||
|
||||
// All enabled spells of the given tier
|
||||
List<Spell> spells = Spell.getSpells(new Spell.TierElementFilter(tier, null));
|
||||
// All enabled spells of the given tier that match this wizard's element
|
||||
List<Spell> specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement()));
|
||||
|
||||
// This code is sooooooo much neater with the new filter system!
|
||||
switch(tier){
|
||||
|
||||
case BASIC:
|
||||
randomiser = rand.nextInt(5);
|
||||
if(randomiser < 4 && !spells.isEmpty()){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){
|
||||
// This means it is more likely for spell books sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
}
|
||||
}else{
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}
|
||||
|
||||
case APPRENTICE:
|
||||
randomiser = rand.nextInt(Wizardry.settings.discoveryMode ? 12 : 10);
|
||||
if(randomiser < 5 && !spells.isEmpty()){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){
|
||||
// This means it is more likely for spell books sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
}
|
||||
}else if(randomiser < 6){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}else if(randomiser < 8){
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 1);
|
||||
}else if(randomiser < 10){
|
||||
EntityEquipmentSlot slot = WizardryUtilities.ARMOUR_SLOTS[rand.nextInt(WizardryUtilities.ARMOUR_SLOTS.length)];
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for armour sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getArmour(this.getElement(), slot));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot));
|
||||
}
|
||||
}else{
|
||||
// Don't need to check for discovery mode here since it is done above
|
||||
return new ItemStack(WizardryItems.identification_scroll);
|
||||
}
|
||||
|
||||
case ADVANCED:
|
||||
randomiser = rand.nextInt(12);
|
||||
if(randomiser < 5 && !spells.isEmpty()){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){
|
||||
// This means it is more likely for spell books sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
}
|
||||
}else if(randomiser < 6){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}else if(randomiser < 8){
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 2);
|
||||
}else{
|
||||
List<Item> upgrades = new ArrayList<Item>(WandHelper.getSpecialUpgrades());
|
||||
randomiser = rand.nextInt(upgrades.size());
|
||||
return new ItemStack(upgrades.get(randomiser));
|
||||
}
|
||||
|
||||
case MASTER:
|
||||
// If a regular wizard rolls a master trade, it can only be a simple master wand or a tome of arcana
|
||||
randomiser = this.getElement() != Element.MAGIC ? rand.nextInt(8) : 5 + rand.nextInt(3);
|
||||
|
||||
if(randomiser < 5 && this.getElement() != Element.MAGIC && !specialismSpells.isEmpty()){
|
||||
// Master spells can only be sold by a specialist in that element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
|
||||
}else if(randomiser < 6){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// Master elemental wands can only be sold by a specialist in that element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.master_wand);
|
||||
}
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 3);
|
||||
}
|
||||
}
|
||||
|
||||
return new ItemStack(Blocks.STONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProfession(VillagerProfession prof) {
|
||||
// Disables Forge's stuff.
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata){
|
||||
|
||||
livingdata = super.onInitialSpawn(difficulty, livingdata);
|
||||
|
||||
textureIndex = this.rand.nextInt(6);
|
||||
|
||||
if(rand.nextBoolean()){
|
||||
this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]);
|
||||
}else{
|
||||
this.setElement(Element.MAGIC);
|
||||
}
|
||||
|
||||
Element element = this.getElement();
|
||||
|
||||
// Adds armour.
|
||||
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
|
||||
this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot)));
|
||||
}
|
||||
|
||||
// Default chance is 0.085f, for reference.
|
||||
for(EntityEquipmentSlot slot : EntityEquipmentSlot.values()) this.setDropChance(slot, 0.0f);
|
||||
|
||||
// All wizards know magic missile, even if it is disabled.
|
||||
spells.add(Spells.magic_missile);
|
||||
|
||||
Tier maxTier = populateSpells(spells, element, 3, rand);
|
||||
|
||||
// Now done after the spells so it can take the tier into account.
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryUtilities.getWand(maxTier, element)));
|
||||
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds n random spells to the given list. The spells will be of the given element if possible. Extracted as a
|
||||
* separate function since it was the same in both EntityWizard and EntityEvilWizard.
|
||||
* @param spells The spell list to be populated.
|
||||
* @param e The element that the spells should belong to, or {@link Element#MAGIC} for a random element each time.
|
||||
* @param n The number of spells to add.
|
||||
* @param random A random number generator to use.
|
||||
* @return The tier of the highest-tier spell that was added to the list.
|
||||
*/
|
||||
static Tier populateSpells(List<Spell> spells, Element e, int n, Random random){
|
||||
|
||||
// This is the tier of the highest tier spell added.
|
||||
Tier maxTier = Tier.BASIC;
|
||||
|
||||
List<Spell> npcSpells = Spell.getSpells(Spell.npcSpells);
|
||||
|
||||
for(int i=0; i<3; i++){
|
||||
|
||||
Tier tier;
|
||||
// If the wizard has no element, it picks a random one each time.
|
||||
Element element = e == Element.MAGIC ? Element.values()[random.nextInt(Element.values().length)] : e;
|
||||
|
||||
int randomiser = random.nextInt(20);
|
||||
|
||||
// Uses its own special weighting
|
||||
if(randomiser < 10){
|
||||
tier = Tier.BASIC;
|
||||
}else if(randomiser < 16){
|
||||
tier = Tier.APPRENTICE;
|
||||
}else{
|
||||
tier = Tier.ADVANCED;
|
||||
}
|
||||
|
||||
if(tier.ordinal() > maxTier.ordinal()) maxTier = tier;
|
||||
|
||||
// Finds all the spells of the chosen tier and element
|
||||
List<Spell> list = Spell.getSpells(new Spell.TierElementFilter(tier, element));
|
||||
// Keeps only spells which can be cast by NPCs
|
||||
list.retainAll(npcSpells);
|
||||
// Removes spells that the wizard already has
|
||||
list.removeAll(spells);
|
||||
|
||||
// Ensures the tier chosen actually has spells in it. (isEmpty() is exactly the same as size() == 0)
|
||||
if(list.isEmpty()){
|
||||
// If there are no spells applicable, tier and element restrictions are removed to give maximum
|
||||
// possibility of there being an applicable spell.
|
||||
list = npcSpells;
|
||||
// Removes spells that the wizard already has
|
||||
list.removeAll(spells);
|
||||
}
|
||||
|
||||
// If the list is still empty now, there must be less than 3 enabled spells that can be cast by wizards
|
||||
// (excluding magic missile). In this case, having empty slots seems reasonable.
|
||||
if(!list.isEmpty()) spells.add(list.get(random.nextInt(list.size())));
|
||||
|
||||
}
|
||||
|
||||
return maxTier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
data.writeInt(textureIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
textureIndex = data.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float damage){
|
||||
|
||||
if(source.getEntity() instanceof EntityPlayer){
|
||||
((EntityPlayer)source.getEntity()).addStat(WizardryAchievements.anger_wizard);
|
||||
}
|
||||
|
||||
return super.attackEntityFrom(source, damage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of blocks that are part of this wizard's tower. If a player breaks any of these blocks, the wizard
|
||||
* will get angry and attack them.
|
||||
* @param blocks A Set of BlockPos objects representing the blocks in the tower.
|
||||
*/
|
||||
public void setTowerBlocks(Set<BlockPos> blocks){
|
||||
this.towerBlocks = blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the block at the given coordinates is part of this wizard's tower.
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @return
|
||||
*/
|
||||
public boolean isBlockPartOfTower(BlockPos pos){
|
||||
if(this.towerBlocks == null) return false;
|
||||
// Uses .equals() rather than == so this will work fine.
|
||||
return this.towerBlocks.contains(pos);
|
||||
}
|
||||
|
||||
// EntityVillager overrides (that don't add features)
|
||||
|
||||
@Override public boolean isMating(){ return false; }
|
||||
@Override public void setMating(boolean p_70947_1_){}
|
||||
@Override public void setPlaying(boolean p_70939_1_){}
|
||||
@Override public boolean isPlaying(){ return false; }
|
||||
@Override public void setLookingForHome(){}
|
||||
// Doesn't say it, but this is in fact nullable.
|
||||
@Override public EntityVillager createChild(EntityAgeable par1EntityAgeable){ return null; }
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override public void setRecipes(MerchantRecipeList par1MerchantRecipeList){}
|
||||
@Override
|
||||
public void onStruckByLightning(EntityLightningBolt lightningBolt){
|
||||
// Restores the normal behaviour, replacing EntityVillager's witch conversion.
|
||||
this.attackEntityFrom(DamageSource.lightningBolt, 5.0F);
|
||||
// Entity's version does something strange with the private fire variable, but since I don't have access this
|
||||
// will probably be fine.
|
||||
this.setFire(8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAIMoveThroughVillage;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.monster.EntityZombie;
|
||||
import net.minecraft.entity.monster.ZombieType;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityZombieMinion extends EntityZombie implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntityZombieMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntityZombieMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntityZombie overrides (EntityZombie is a long class so there are lots of these)
|
||||
|
||||
@Override
|
||||
protected void applyEntityAI()
|
||||
{
|
||||
this.tasks.addTask(6, new EntityAIMoveThroughVillage(this, 1.0D, false));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
@Override public boolean isChild(){ return false; }
|
||||
@Override public void setChild(boolean childZombie){}
|
||||
@Override public ZombieType getZombieType(){ return ZombieType.NORMAL; }
|
||||
@Override public boolean isVillager(){ return false; }
|
||||
@Override public net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession getVillagerTypeForge(){ return null; }
|
||||
@Override protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty){} // They don't have equipment!
|
||||
@Override public void onKillEntity(EntityLivingBase entityLivingIn){} // Turns villagers to zombies in EntityZombie
|
||||
@Override protected void startConversion(int ticks){}
|
||||
@Override public boolean isConverting(){ return false; }
|
||||
@Override protected void convertToVillager(){}
|
||||
@Override protected int getConversionTimeBoost(){ return 0; }
|
||||
@Override public void setChildSize(boolean isChild){}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
|
||||
/** [NYI] Interface for entities that can select between spells based on their current circumstances, to be used in
|
||||
* conjunction with {@link EntityAISelectSpell}. */
|
||||
public interface IIntelligentSpellCaster extends ISpellCaster {
|
||||
|
||||
/** Called from {@link EntityAISelectSpell} to set the spells that the entity can use in its current situation.
|
||||
* Implementors should assign the given list to some internal field and retrieve it when {@link #getSpells()} is
|
||||
* called. */
|
||||
public void setCurrentSpells(List<Spell> spells);
|
||||
|
||||
/** Returns a list of spells that this entity 'knows'. The entity's current spell will be selected from this list
|
||||
* based on the current situation: whether it is attacking, its health, etc. Most likely, you will want to return a
|
||||
* constant list, but it may change for some reason - perhaps if the entity 'learns' a new spell. */
|
||||
public List<Spell> getKnownSpells();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
|
||||
/** Interface for entities that can cast spells. Mainly intended for use by wizard-type entities, but can be
|
||||
* implemented by any subclass of EntityLiving. Designed to be as flexible as possible - ranging from the simplest
|
||||
* use of giving an entity a specific spell as an attack, to a complex AI which selects different spell types
|
||||
* depending on the situation. The only restriction is that the spells must be castable by NPCs.
|
||||
* <p>
|
||||
* This is intended for entities that use {@link EntityAIAttackSpell}. If so, all the spell casting code (including
|
||||
* packets) is handled by that class, and all the implementor needs to do is decide which spell(s) to select.
|
||||
* <p>
|
||||
* This class also allows Wizardry to do all the syncing necessary for continuous spell casting. All the implementor
|
||||
* needs to do is store the actual fields involved.
|
||||
*/
|
||||
/* Perhaps this should be a capability? Though I can't help thinking they're mainly for attaching data to vanilla
|
||||
* classes, rather than custom ones. For now, the main purpose of this is to centralise code within wizardry itself,
|
||||
* and I may as well make it an API feature - but I'm not writing a capability unless someone sees a reason to attach
|
||||
* it to a class they don't own, and I don't see that happening anytime soon. */
|
||||
// For even more fun, combine this with an ISummonedCreature and have skeletons that can cast spells!
|
||||
public interface ISpellCaster {
|
||||
|
||||
/**
|
||||
* Called each time the entity attacks to get the spells that can be cast. For simple implementations, just
|
||||
* return a list of size one containing the spell that the entity uses as an attack, perhaps performing some
|
||||
* simple logic within this method. For more intelligent implementations based on spell types, consider using
|
||||
* {@link IIntelligentSpellCaster} instead.
|
||||
* @return A list of {@link Spell} instances. A random spell from this list will be cast when the entity attacks.
|
||||
* The list will not be modified by the AI class and can therefore be an immutable list. The spells in the list
|
||||
* <b>must</b> be castable by NPCs (i.e. {@link Spell#canBeCastByNPCs()} returns true).
|
||||
*/
|
||||
@Nonnull
|
||||
public List<Spell> getSpells();
|
||||
|
||||
/**
|
||||
* Called each time the entity attacks to get the modifiers to apply to the spell.
|
||||
* @return A {@link SpellModifiers} object representing the modifiers to apply to the spell. If no modifiers are required,
|
||||
* pass in an empty {@code SpellModifiers} object.
|
||||
*/
|
||||
@Nonnull
|
||||
public SpellModifiers getModifiers();
|
||||
|
||||
/** Returns the continuous spell that is currently being cast, or the None spell if there is none. Implementors should simply
|
||||
* store this as a private field and return it here. Will be synced by the AI class, but whether it is saved to NBT
|
||||
* is up to you. If the implementing class does not deal with continuous spells, just return {@link Spells#none}. If the
|
||||
* implementing class only ever uses one continuous spell, do <b>not</b> just return that spell; the field must still
|
||||
* be stored. */
|
||||
@Nonnull
|
||||
public Spell getContinuousSpell();
|
||||
|
||||
/** Sets the continuous spell that is currently being cast, or the None spell if there is none. Implementors should simply
|
||||
* store this as a private field and assign it here. Will be synced by the AI class, but whether it is saved to NBT
|
||||
* is up to you. If the implementing class does not deal with continuous spells, leave this method blank. */
|
||||
public void setContinuousSpell(Spell spell);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.WizardryEventHandler;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.monster.IMob;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
/** Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 1.2, and this
|
||||
* interface allows summoned creatures to extend vanilla (or indeed modded) entity classes, so <code>EntitySummonedZombie</code>
|
||||
* now extends <code>EntityZombie</code>, for example. This change has two major benefits:
|
||||
* <p>
|
||||
* - There is no longer any need for separate render classes, because summoned creatures are now instances of vanilla
|
||||
* types. <i>You don't even need to assign a render class</i> because the supertype should already be assigned the correct one.<br>
|
||||
* - Summoned creature classes are now much more robust when it comes to changes between Minecraft versions, since none
|
||||
* of the vanilla code needs to be copied.
|
||||
* <p>
|
||||
* <b>Summoned creatures that do not emulate vanilla entities do not directly implement this interface</b>. Instead, they
|
||||
* should extend the abstract base implementation, {@link EntitySummonedCreature}.
|
||||
* <p>
|
||||
* All damage dealt by ISummonedCreature instances is redirected via {@link WizardryEventHandler#onLivingAttackEvent(
|
||||
* net.minecraftforge.event.entity.living.LivingAttackEvent) WizardryEventHandler.onLivingAttackEvent(LivingAttackEvent)}
|
||||
* and replaced by an instance of {@link electroblob.wizardry.util.IElementalDamage IElementalDamage} with the summoner
|
||||
* of that creature as the source rather than the creature itself. This means that kills by summoned creatures register
|
||||
* as player kills, dropping xp and rare loot.
|
||||
* <p>
|
||||
* Though this system is a lot better than the previous system, <i>it is not a perfect solution</i>. The old
|
||||
* EntitySummonedCreature class overrode some methods from Entity in order to add shared functionality, but this cannot
|
||||
* be done with an interface. To get around this problem, this interface contains 5 delegate methods that do the same
|
||||
* things, with the aim of centralising as much code as possible, even though it is not automatically applied.
|
||||
* <b>Implementing classes must override the corresponding methods from Entity, and within them, call the appropriate
|
||||
* delegate method in this interface.</b> It is impossible to enforce this condition, but the summoned creature will not
|
||||
* work properly unless it is adhered to. <i>The position of the delegate method call is unimportant, but by convention
|
||||
* it is usually at the start of the calling method, which avoids it being unintentionally skipped by a return statement.</i>
|
||||
* <p>
|
||||
* It is recommended that when implementing this interface, you begin by copying {@link EntitySummonedCreature} to ensure
|
||||
* all the relevant methods are duplicated. You can then change the superclass, override any additional methods and add
|
||||
* functionality to any that are already overridden. You will always want to override the AI methods at the very least.
|
||||
* <p>
|
||||
* Due to the limitations of interfaces, some methods that really ought to be protected are public. These are clearly
|
||||
* marked as 'Internal, DO NOT CALL'. <b>Don't call them, only implement them.</b>
|
||||
* @since Wizardry 1.2
|
||||
* @author Electroblob */
|
||||
/*
|
||||
* Quite honestly, this is not what default methods are really for. However, this is modding, and in modding some
|
||||
* sacrifices have to be made when it comes to Java style - because adding on to a pre-existing program is not a
|
||||
* good way of doing this sort of thing anyway, but we have no choice about that!
|
||||
*/
|
||||
public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
|
||||
// Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that.
|
||||
String NAMEPLATE_TRANSLATION_KEY = "entity.wizardry.summonedcreature.nameplate";
|
||||
|
||||
// Setters and getters. The subclass fields that these access should be private.
|
||||
|
||||
/** Sets the lifetime of the summoned creature in ticks. */
|
||||
void setLifetime(int ticks);
|
||||
|
||||
/** Returns the lifetime of the summoned creature in ticks. Allows primarily for duration multiplier support, but
|
||||
* also for example the skeleton legion spell which lasts for 60 seconds instead of the usual 30. Syncing and saving
|
||||
* is done automatically. As of Wizardry 1.2, despawning is handled in ISummonedCreature; see
|
||||
* {@link ISummonedCreature#onDespawn()} for details. */
|
||||
int getLifetime();
|
||||
|
||||
/** Sets the WeakReference object which refers to the owner of this summoned creature. Internal, don't call
|
||||
* unless you know what you are doing. */
|
||||
void setCasterReference(WeakReference<EntityLivingBase> reference);
|
||||
|
||||
/** Returns a WeakReference object which refers to the owner of this summoned creature. Subclasses should store this
|
||||
* as a private field. This may be null; as such it is preferable to use {@link ISummonedCreature#getCaster()} to get
|
||||
* the caster object itself. */
|
||||
@Nullable
|
||||
WeakReference<EntityLivingBase> getCasterReference();
|
||||
|
||||
/** Internal, DO NOT CALL. */
|
||||
void setCasterUUID(UUID uuid);
|
||||
/** Internal, DO NOT CALL. This is for loading purposes only and is not usually synchronised. */
|
||||
UUID getCasterUUID();
|
||||
|
||||
/** Returns the EntityLivingBase that summoned this creature, or null if it no longer exists. Cases where the
|
||||
* entity may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported
|
||||
* to another dimension, or this creature simply had no caster in the first place. <i>This is the correct method
|
||||
* to use to get the owner of this summoned creature. */
|
||||
@Nullable
|
||||
default EntityLivingBase getCaster(){
|
||||
return getCasterReference() == null ? null : getCasterReference().get();
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
/**
|
||||
* Called by the server when constructing the spawn packet.
|
||||
* Data should be added to the provided stream.
|
||||
* <b>Implementors must call super when overriding.</b>
|
||||
*
|
||||
* @param buffer The packet data stream
|
||||
*/
|
||||
@Override
|
||||
default void writeSpawnData(ByteBuf buffer) {
|
||||
buffer.writeInt(getCaster() != null ? getCaster().getEntityId() : -1);
|
||||
buffer.writeInt(getLifetime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the client when it receives a Entity spawn packet.
|
||||
* Data should be read out of the stream in the same way as it was written.
|
||||
* <b>Implementors must call super when overriding.</b>
|
||||
*
|
||||
* @param additionalData The packet data stream
|
||||
*/
|
||||
@Override
|
||||
default void readSpawnData(ByteBuf buffer) {
|
||||
int id = buffer.readInt();
|
||||
// We're on the client side here, so we can safely use Minecraft.getMinecraft().theWorld via proxies.
|
||||
if(id > -1) setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)Wizardry.proxy.getTheWorld().getEntityByID(id)));
|
||||
setLifetime(buffer.readInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this creature as the
|
||||
* attacker. Also allows implementors to override it if they wish to do so.
|
||||
*/
|
||||
default boolean isValidTarget(Entity target){
|
||||
return WizardryUtilities.isValidTarget(this.getCaster(), target);
|
||||
}
|
||||
|
||||
/** Returns a entity selector to be passed into AI methods. Normally, this should not be overridden, but it is
|
||||
* possible for implementors to override this in order to do something special when selecting a target. */
|
||||
default Predicate<Entity> getTargetSelector(){
|
||||
|
||||
return new Predicate<Entity>(){
|
||||
|
||||
public boolean apply(Entity entity){
|
||||
// TODO: Backport invisibility check (also in wizards)
|
||||
// If the target is valid and not invisible...
|
||||
if(!entity.isInvisible() && isValidTarget(entity)){
|
||||
|
||||
//... and is a player, they can be attacked, since players can't be in the whitelist or the blacklist.
|
||||
if(entity instanceof EntityPlayer) return true;
|
||||
|
||||
//... and is a mob, a summoned creature, a wizard ...
|
||||
if((entity instanceof IMob || entity instanceof ISummonedCreature || (entity instanceof EntityWizard && !(getCaster() instanceof EntityWizard))
|
||||
// ... or in the whitelist ...
|
||||
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT)))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this creature has existed for 1 tick, effectively when it has just been spawned. Normally used
|
||||
* to add particles, sounds, etc.
|
||||
*/
|
||||
void onSpawn();
|
||||
|
||||
/**
|
||||
* Called when this summoned creature vanishes. Normally used to add particles, sounds, etc.
|
||||
*/
|
||||
void onDespawn();
|
||||
|
||||
/** Whether this creature should spawn a subtle black swirl particle effect while alive. */
|
||||
boolean hasParticleEffect();
|
||||
|
||||
/** Called from the event handler after the damage change is applied. Does nothing by default, but can be overridden
|
||||
* to do something when a successful attack is made. This was added because the event-based damage source system
|
||||
* can cause parts of attackEntityAsMob not to fire, since attackEntityFrom is intercepted and canceled.
|
||||
* <p>
|
||||
* Usage examples: {@link EntitySliverfishMinion} uses this to summon more silverfish if the target is killed,
|
||||
* {@link EntitySkeletonMinion} and {@link EntitySpiderMinion} use this to add potion effects to the target. */
|
||||
default void onSuccessfulAttack(EntityLivingBase target){};
|
||||
|
||||
// Delegates
|
||||
|
||||
/** Implementors should call this from writeEntityToNBT. Can be overridden as long as super is called, but there's
|
||||
* very little point in doing that since anything extra could just be added to writeEntityToNBT anyway. */
|
||||
default void writeNBTDelegate(NBTTagCompound tagcompound){
|
||||
if(this.getCaster() != null){
|
||||
tagcompound.setUniqueId("casterUUID", this.getCaster().getUniqueID());
|
||||
}
|
||||
tagcompound.setInteger("lifetime", getLifetime());
|
||||
}
|
||||
|
||||
/** Implementors should call this from readEntityFromNBT. Can be overridden as long as super is called, but there's
|
||||
* very little point in doing that since anything extra could just be added to readEntityFromNBT anyway. */
|
||||
default void readNBTDelegate(NBTTagCompound tagcompound){
|
||||
this.setCasterUUID(tagcompound.getUniqueId("casterUUID"));
|
||||
this.setLifetime(tagcompound.getInteger("lifetime"));
|
||||
}
|
||||
|
||||
/** Implementors should call this from setRevengeTarget, and call super.setRevengeTarget if and only if this method
|
||||
* returns <b>true</b>. */
|
||||
default boolean shouldRevengeTarget(EntityLivingBase entity){
|
||||
// Allows the config to prevent minions from revenge-targeting their owners.
|
||||
return entity != this.getCaster() || Wizardry.settings.minionRevengeTargeting;
|
||||
}
|
||||
|
||||
/** Implementors should call this from onUpdate. Can be overridden as long as super is called, but there's
|
||||
* very little point in doing that since anything extra could just be added to onUpdate anyway. */
|
||||
default void updateDelegate(){
|
||||
|
||||
if(!(this instanceof Entity)) throw new ClassCastException("Implementations of ISummonedCreature must extend Entity!");
|
||||
|
||||
Entity thisEntity = ((Entity)this);
|
||||
|
||||
if(this.getCaster() == null && this.getCasterUUID() != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(thisEntity.worldObj, getCasterUUID());
|
||||
if(entity instanceof EntityLivingBase){
|
||||
this.setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)entity));
|
||||
}
|
||||
}
|
||||
|
||||
if(thisEntity.ticksExisted == 1){
|
||||
this.onSpawn();
|
||||
}
|
||||
|
||||
if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() != -1){
|
||||
this.onDespawn();
|
||||
thisEntity.setDead();
|
||||
}
|
||||
|
||||
if(this.hasParticleEffect() && thisEntity.worldObj.isRemote && thisEntity.worldObj.rand.nextInt(8) == 0)
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, thisEntity.worldObj, thisEntity.posX, thisEntity.posY + thisEntity.worldObj.rand.nextDouble()*1.5, thisEntity.posZ, 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.0f, 0.0f);
|
||||
|
||||
}
|
||||
|
||||
/** Implementors should call this from processInteract, and call super.processInteract if and only if this method
|
||||
* returns <b>false</b>. */
|
||||
default boolean interactDelegate(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
|
||||
WizardData properties = WizardData.get(player);
|
||||
// Selects one of the player's minions.
|
||||
if(player.isSneaking() && stack != null && stack.getItem() instanceof ItemWand){
|
||||
|
||||
if(!player.worldObj.isRemote && properties != null && this.getCaster() == player){
|
||||
|
||||
if(properties.selectedMinion != null && properties.selectedMinion.get() == this){
|
||||
// Deselects the selected minion if right-clicked again
|
||||
properties.selectedMinion = null;
|
||||
}else{
|
||||
// Selects this minion
|
||||
properties.selectedMinion = new WeakReference<ISummonedCreature>(this);
|
||||
}
|
||||
properties.sync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
/**
|
||||
* Same as {@link EntityMagicProjectile}, but with an additional blast multiplier field which is synced and saved to
|
||||
* allow for the spread of particles to be changed depending on the blast area.
|
||||
* @author Electroblob
|
||||
* @since Wizardry 1.2
|
||||
*/
|
||||
public abstract class EntityBomb extends EntityMagicProjectile implements IEntityAdditionalSpawnData {
|
||||
|
||||
/** The entity blast multiplier. This is now synced and saved centrally from {@link EntityBomb}. */
|
||||
public float blastMultiplier = 1.0f;
|
||||
|
||||
public EntityBomb(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityBomb(World world, EntityLivingBase thrower) {
|
||||
super(world, thrower);
|
||||
}
|
||||
|
||||
public EntityBomb(World world, EntityLivingBase thrower, float damageMultiplier, float blastMultiplier) {
|
||||
super(world, thrower, damageMultiplier);
|
||||
this.blastMultiplier = blastMultiplier;
|
||||
}
|
||||
|
||||
public EntityBomb(World par1World, double par2, double par4, double par6) {
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buffer) {
|
||||
buffer.writeFloat(blastMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buffer) {
|
||||
blastMultiplier = buffer.readFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
blastMultiplier = nbttagcompound.getFloat("blastMultiplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setFloat("blastMultiplier", blastMultiplier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityDarknessOrb extends EntityMagicProjectile
|
||||
{
|
||||
public EntityDarknessOrb(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityDarknessOrb(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityDarknessOrb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier);
|
||||
}
|
||||
|
||||
public EntityDarknessOrb(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSpeed(){
|
||||
return 0.5F;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult RayTraceResult)
|
||||
{
|
||||
Entity target = RayTraceResult.entityHit;
|
||||
|
||||
if (target != null && !MagicDamage.isEntityImmune(DamageType.WITHER, target))
|
||||
{
|
||||
float damage = 8 * damageMultiplier;
|
||||
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.WITHER).setProjectile(), damage);
|
||||
|
||||
if(target instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.WITHER, target)) ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER, 150, 1));
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_WITHER_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(worldObj.isRemote){
|
||||
float brightness = rand.nextFloat()*0.2f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0, 20 + rand.nextInt(10), brightness, 0.0f, brightness);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 150){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
// Cancels out the slowdown effect in EntityThrowable
|
||||
this.motionX /= 0.99;
|
||||
this.motionY /= 0.99;
|
||||
this.motionZ /= 0.99;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of gravity to apply to the thrown entity with each tick.
|
||||
*/
|
||||
protected float getGravityVelocity()
|
||||
{
|
||||
return 0.0F;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityDart extends EntityMagicArrow
|
||||
{
|
||||
/** Basic shell constructor. Should only be used by the client. */
|
||||
public EntityDart(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
/** Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this
|
||||
* constructor and then call setVelocity() as that method is, bizarrely, client-side only. */
|
||||
public EntityDart(World world, double x, double y, double z)
|
||||
{
|
||||
super(world, x, y, z);
|
||||
}
|
||||
|
||||
/** Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be altered
|
||||
* slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on easy, 6 on
|
||||
* normal and 2 on hard difficulty. */
|
||||
public EntityDart(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, target, speed, aimingError, damageMultiplier);
|
||||
}
|
||||
|
||||
/** Creates a projectile pointing in the direction the caster is looking, with the given speed.
|
||||
* USE THIS CONSTRUCTOR FOR NORMAL SPELLS. */
|
||||
public EntityDart(World world, EntityLivingBase caster, float speed, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, speed, damageMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
//Adds a weakness effect to the target.
|
||||
entityHit.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 200, 1, false, false));
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(){
|
||||
this.playSound(SoundEvents.ENTITY_ARROW_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
|
||||
if(this.worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.LEAF, worldObj, this.posX, this.posY, this.posZ, 0, -0.03, 0, 10 + rand.nextInt(5));
|
||||
}
|
||||
}
|
||||
|
||||
// Replicates the original behaviour of staying stuck in block for a few seconds before disappearing.
|
||||
@Override
|
||||
public void tickInGround(){
|
||||
if(this.ticksInGround > 60){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDamage(){
|
||||
return 4.0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageType getDamageType(){
|
||||
return DamageType.MAGIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGravity(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDeceleration(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityFirebolt extends EntityMagicProjectile
|
||||
{
|
||||
public EntityFirebolt(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityFirebolt(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityFirebolt(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier);
|
||||
}
|
||||
|
||||
public EntityFirebolt(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace)
|
||||
{
|
||||
Entity entityHit = rayTrace.entityHit;
|
||||
|
||||
if (entityHit != null)
|
||||
{
|
||||
float damage = 5 * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(), damage);
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) entityHit.setFire(5);
|
||||
}
|
||||
|
||||
this.playSound(SoundEvents.BLOCK_LAVA_POP, 2, 0.8f + rand.nextFloat()*0.3f);
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0;i<8;i++){
|
||||
worldObj.spawnParticle(EnumParticleTypes.LAVA, this.posX + rand.nextFloat() - 0.5, this.posY + this.height/2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<4; i++){
|
||||
worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + rand.nextFloat()*0.2 - 0.1, this.posY + this.height/2 + rand.nextFloat()*0.2 - 0.1, this.posZ + rand.nextFloat()*0.2 - 0.1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 8){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of gravity to apply to the thrown entity with each tick.
|
||||
*/
|
||||
@Override
|
||||
protected float getGravityVelocity()
|
||||
{
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
@Override
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityFirebomb extends EntityBomb {
|
||||
|
||||
public EntityFirebomb(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityFirebomb(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityFirebomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, float blastMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier);
|
||||
}
|
||||
|
||||
public EntityFirebomb(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this EntityThrowable hits a block or entity.
|
||||
*/
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult)
|
||||
{
|
||||
Entity entityHit = par1RayTraceResult.entityHit;
|
||||
|
||||
if (entityHit != null)
|
||||
{
|
||||
// This is if the firebomb gets a direct hit
|
||||
float damage = 5 * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(), damage);
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) entityHit.setFire(10);
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
for(int i=0;i<60*blastMultiplier;i++){
|
||||
//this.worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0, 0, 0);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, worldObj, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0, 0, 0, 15 + rand.nextInt(5), 2 + rand.nextFloat(), 0, 0);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0.0d, 0.0d, 0.0d, 0, 1.0f, 0.2f + rand.nextFloat()*0.4f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
|
||||
this.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
|
||||
|
||||
double range = 3.0d*blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
if(target != entityHit && target != this.getThrower() && !MagicDamage.isEntityImmune(DamageType.FIRE, target)){
|
||||
// Splash damage does not count as projectile damage
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE), 4.0f * damageMultiplier);
|
||||
target.setFire(7);
|
||||
}
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityForceArrow extends EntityMagicArrow {
|
||||
|
||||
/** Basic shell constructor. Should only be used by the client. */
|
||||
public EntityForceArrow(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
/** Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this
|
||||
* constructor and then call setVelocity() as that method is, bizarrely, client-side only. */
|
||||
public EntityForceArrow(World world, double x, double y, double z)
|
||||
{
|
||||
super(world, x, y, z);
|
||||
}
|
||||
|
||||
/** Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be altered
|
||||
* slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on easy, 6 on
|
||||
* normal and 2 on hard difficulty. */
|
||||
public EntityForceArrow(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, target, speed, aimingError, damageMultiplier);
|
||||
}
|
||||
|
||||
/** Creates a projectile pointing in the direction the caster is looking, with the given speed.
|
||||
* USE THIS CONSTRUCTOR FOR NORMAL SPELLS. */
|
||||
public EntityForceArrow(World world, EntityLivingBase caster, float speed, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, speed, damageMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInGround(){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(){
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
if (this.ticksExisted > 20){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDamage(){
|
||||
return 7.0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageType getDamageType(){
|
||||
return DamageType.FORCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGravity(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDeceleration(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityForceOrb extends EntityMagicProjectile {
|
||||
|
||||
/** The entity blast multiplier. In this particular case, it doesn't need syncing, so this class doesn't extend
|
||||
* EntityBlastProjectile. */
|
||||
public float blastMultiplier;
|
||||
|
||||
public EntityForceOrb(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityForceOrb(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityForceOrb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, float blastMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier);
|
||||
this.blastMultiplier = blastMultiplier;
|
||||
}
|
||||
|
||||
public EntityForceOrb(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this EntityThrowable hits a block or entity.
|
||||
*/
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult){
|
||||
|
||||
if (par1RayTraceResult.entityHit != null)
|
||||
{
|
||||
// This is if the force orb gets a direct hit
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
if(this.worldObj.isRemote){
|
||||
for(int j=0; j<20; j++){
|
||||
float brightness = 0.5f + (rand.nextFloat()/2);
|
||||
double x = this.posX - 0.25d + (rand.nextDouble()/2);
|
||||
double y = this.posY - 0.25d + (rand.nextDouble()/2);
|
||||
double z = this.posZ - 0.25d + (rand.nextDouble()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, x, y, z, (x - this.posX)*2, (y - this.posY)*2, (z - this.posZ)*2, 6, brightness, 1.0f, brightness + 0.2f);
|
||||
}
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
}
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
// 2 gives a cool flanging effect!
|
||||
float pitch = this.rand.nextFloat() * 0.2F + 0.3F;
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.5F, pitch);
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.5F, pitch - 0.01f);
|
||||
|
||||
double blastRadius = 4.0d*blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(blastRadius, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
if(target != this.getThrower()){
|
||||
|
||||
double velY = target.motionY;
|
||||
|
||||
double dx = this.posX-target.posX > 0? -0.5 - (this.posX-target.posX)/8 : 0.5 - (this.posX-target.posX)/8;
|
||||
double dz = this.posZ-target.posZ > 0? -0.5 - (this.posZ-target.posZ)/8 : 0.5 - (this.posZ-target.posZ)/8;
|
||||
|
||||
float damage = 4*damageMultiplier;
|
||||
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.BLAST), damage);
|
||||
|
||||
target.motionX = dx;
|
||||
target.motionY = velY + 0.4;
|
||||
target.motionZ = dz;
|
||||
}
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
blastMultiplier = nbttagcompound.getFloat("blastMultiplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setFloat("blastMultiplier", blastMultiplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceCharge extends EntityBomb {
|
||||
|
||||
public EntityIceCharge(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityIceCharge(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityIceCharge(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, float blastMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier);
|
||||
}
|
||||
|
||||
public EntityIceCharge(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this EntityThrowable hits a block or entity.
|
||||
*/
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult)
|
||||
{
|
||||
Entity entityHit = par1RayTraceResult.entityHit;
|
||||
|
||||
if (entityHit != null)
|
||||
{
|
||||
// This is if the ice charge gets a direct hit
|
||||
float damage = 4 * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FROST).setProjectile(), damage);
|
||||
|
||||
if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
|
||||
((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(WizardryPotions.frost, 120, 1));
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
for(int i=0;i<30*blastMultiplier;i++){
|
||||
float brightness = 0.4f + rand.nextFloat()*0.5f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.ICE, worldObj, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0.0d, 0.0d, 0.0d, 35);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0.0d, 0.0d, 0.0d, 0, brightness, brightness+0.1f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5f, rand.nextFloat() * 0.4f + 0.6f);
|
||||
this.playSound(WizardrySounds.SPELL_ICE, 1.2f, rand.nextFloat() * 0.4f + 1.2f);
|
||||
|
||||
double radius = 3.0d*blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
// Slows targets
|
||||
for(EntityLivingBase target : targets){
|
||||
if(target != entityHit && target != this.getThrower()){
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, target)) target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Places snow and ice on ground.
|
||||
for(int i=-1; i<2; i++){
|
||||
for(int j=-1; j<2; j++){
|
||||
|
||||
BlockPos pos = new BlockPos(this.posX + i, this.posY, this.posZ + j);
|
||||
|
||||
int y = WizardryUtilities.getNearestFloorLevelB(worldObj, pos, 7);
|
||||
|
||||
pos = new BlockPos(pos.getX(), y, pos.getZ());
|
||||
|
||||
double dist = this.getDistance(pos.getX(), pos.getY(), pos.getZ());
|
||||
|
||||
// Randomised with weighting so that the nearer the block the more likely it is to be snowed.
|
||||
if(y != -1 && rand.nextInt((int)dist*2 + 1) < 1 && dist < 2){
|
||||
if(worldObj.getBlockState(pos.down()).getBlock() == Blocks.WATER){
|
||||
worldObj.setBlockState(pos.down(), Blocks.ICE.getDefaultState());
|
||||
}else{
|
||||
// Don't need to check whether the block at pos can be replaced since getNearestFloorLevelB
|
||||
// only ever returns floors with air above them.
|
||||
worldObj.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Releases shards
|
||||
for(int i=0; i<10; i++){
|
||||
double dx = rand.nextDouble()-0.5;
|
||||
double dy = rand.nextDouble()-0.5;
|
||||
double dz = rand.nextDouble()-0.5;
|
||||
EntityIceShard iceshard = new EntityIceShard(worldObj, this.posX + dx, this.posY + dy, this.posZ + dz);
|
||||
iceshard.motionX = dx;
|
||||
iceshard.motionY = dy;
|
||||
iceshard.motionZ = dz;
|
||||
iceshard.setShootingEntity(this.getThrower());
|
||||
iceshard.damageMultiplier = this.damageMultiplier;
|
||||
worldObj.spawnEntityInWorld(iceshard);
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceLance extends EntityMagicArrow {
|
||||
|
||||
/** Basic shell constructor. Should only be used by the client. */
|
||||
public EntityIceLance(World world) {
|
||||
super(world);
|
||||
this.setKnockbackStrength(1);
|
||||
}
|
||||
|
||||
/** Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this
|
||||
* constructor and then call setVelocity() as that method is, bizarrely, client-side only. */
|
||||
public EntityIceLance(World world, double x, double y, double z)
|
||||
{
|
||||
super(world, x, y, z);
|
||||
this.setKnockbackStrength(1);
|
||||
}
|
||||
|
||||
/** Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be altered
|
||||
* slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on easy, 6 on
|
||||
* normal and 2 on hard difficulty. */
|
||||
public EntityIceLance(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, target, speed, aimingError, damageMultiplier);
|
||||
this.setKnockbackStrength(1);
|
||||
}
|
||||
|
||||
/** Creates a projectile pointing in the direction the caster is looking, with the given speed.
|
||||
* USE THIS CONSTRUCTOR FOR NORMAL SPELLS. */
|
||||
public EntityIceLance(World world, EntityLivingBase caster, float speed, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, speed, damageMultiplier);
|
||||
this.setKnockbackStrength(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
|
||||
// Adds a freeze effect to the target.
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit)) entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 0));
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(){
|
||||
// Adds a particle effect when the ice lance hits a block.
|
||||
if(this.worldObj.isRemote){
|
||||
for(int j=0; j<10; j++){
|
||||
double x = this.posX - 0.25d + (rand.nextDouble()/2);
|
||||
double y = this.posY - 0.25d + (rand.nextDouble()/2);
|
||||
double z = this.posZ - 0.25d + (rand.nextDouble()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.ICE, worldObj, x, y, z, x - this.posX, y - this.posY, z - this.posZ, 20 + rand.nextInt(10));
|
||||
}
|
||||
}
|
||||
// Parameters for sound: sound event name, volume, pitch.
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.0F, rand.nextFloat() * 0.4F + 1.2F);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInGround(){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDamage(){
|
||||
return 10.0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageType getDamageType(){
|
||||
return DamageType.FROST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGravity(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDeceleration(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doOverpenetration(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceShard extends EntityMagicArrow {
|
||||
|
||||
/** Basic shell constructor. Should only be used by the client. */
|
||||
public EntityIceShard(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
/** Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this
|
||||
* constructor and then call setVelocity() as that method is, bizarrely, client-side only. */
|
||||
public EntityIceShard(World world, double x, double y, double z)
|
||||
{
|
||||
super(world, x, y, z);
|
||||
}
|
||||
|
||||
/** Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be altered
|
||||
* slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on easy, 6 on
|
||||
* normal and 2 on hard difficulty. */
|
||||
public EntityIceShard(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, target, speed, aimingError, damageMultiplier);
|
||||
}
|
||||
|
||||
/** Creates a projectile pointing in the direction the caster is looking, with the given speed.
|
||||
* USE THIS CONSTRUCTOR FOR NORMAL SPELLS. */
|
||||
public EntityIceShard(World world, EntityLivingBase caster, float speed, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, speed, damageMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
|
||||
// Adds a freeze effect to the target.
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit)) entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 0));
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(){
|
||||
// Adds a particle effect when the ice shard hits a block.
|
||||
if(this.worldObj.isRemote){
|
||||
for(int j=0; j<10; j++){
|
||||
double x = this.posX - 0.25d + (rand.nextDouble()/2);
|
||||
double y = this.posY - 0.25d + (rand.nextDouble()/2);
|
||||
double z = this.posZ - 0.25d + (rand.nextDouble()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.ICE, worldObj, x, y, z, x - this.posX, y - this.posY, z - this.posZ, 20 + rand.nextInt(10));
|
||||
}
|
||||
}
|
||||
// Parameters for sound: sound event name, volume, pitch.
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.0F, rand.nextFloat() * 0.4F + 1.2F);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDamage(){
|
||||
return 6.0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageType getDamageType(){
|
||||
return DamageType.FROST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGravity(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDeceleration(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityLightningArrow extends EntityMagicArrow {
|
||||
|
||||
/** Basic shell constructor. Should only be used by the client. */
|
||||
public EntityLightningArrow(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
/** Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this
|
||||
* constructor and then call setVelocity() as that method is, bizarrely, client-side only. */
|
||||
public EntityLightningArrow(World world, double x, double y, double z)
|
||||
{
|
||||
super(world, x, y, z);
|
||||
}
|
||||
|
||||
/** Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be altered
|
||||
* slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on easy, 6 on
|
||||
* normal and 2 on hard difficulty. */
|
||||
public EntityLightningArrow(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, target, speed, aimingError, damageMultiplier);
|
||||
}
|
||||
|
||||
/** Creates a projectile pointing in the direction the caster is looking, with the given speed.
|
||||
* USE THIS CONSTRUCTOR FOR NORMAL SPELLS. */
|
||||
public EntityLightningArrow(World world, EntityLivingBase caster, float speed, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, speed, damageMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
|
||||
if(worldObj.isRemote){
|
||||
for(int j=0;j<8;j++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + rand.nextFloat() - 0.5, this.posY + this.height/2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0, 3);
|
||||
}
|
||||
}
|
||||
/* Pretty sure this needn't be here, probably missed it when I implemented the damage type system.
|
||||
if(entityHit instanceof EntityCreeper && !((EntityCreeper)entityHit).getPowered()){
|
||||
entityHit.getDataWatcher().updateObject(17, Byte.valueOf((byte)1));
|
||||
if(this.getShootingEntity() instanceof EntityPlayer) ((EntityPlayer)this.getShootingEntity()).addStat(Wizardry.chargeCreeper);
|
||||
}
|
||||
*/
|
||||
this.playSound(WizardrySounds.SPELL_SPARK, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
|
||||
if (this.ticksExisted > 20){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
if(worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX, this.posY, this.posZ, 0, 0, 0, 3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDamage(){
|
||||
return 7.0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageType getDamageType(){
|
||||
return DamageType.SHOCK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGravity(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDeceleration(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityLightningDisc extends EntityMagicProjectile
|
||||
{
|
||||
public EntityLightningDisc(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityLightningDisc(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityLightningDisc(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier);
|
||||
}
|
||||
|
||||
public EntityLightningDisc(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
this.width = 2.0f;
|
||||
this.height = 0.5f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSpeed(){
|
||||
return 1.2f;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult mop)
|
||||
{
|
||||
Entity entityHit = mop.entityHit;
|
||||
|
||||
if (entityHit != null)
|
||||
{
|
||||
float damage = 12 * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK), damage);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_SPARK, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
|
||||
if(mop.typeOfHit == RayTraceResult.Type.BLOCK) this.setDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0;i<8;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + rand.nextFloat()*2 - 1, this.posY, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0, 3);
|
||||
//worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + rand.nextFloat() - 0.5, this.posY + this.height/2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.isCollided && !worldObj.isRemote){
|
||||
|
||||
double seekingRange = 5.0d;
|
||||
|
||||
List<EntityLivingBase> entities = WizardryUtilities.getEntitiesWithinRadius(seekingRange, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
Entity target = null;
|
||||
|
||||
for(Entity possibleTarget : entities){
|
||||
// Decides if current entity should be replaced.
|
||||
if(target == null || this.getDistanceToEntity(target) > this.getDistanceToEntity(possibleTarget)){
|
||||
// Decides if new entity is a valid target.
|
||||
if(WizardryUtilities.isValidTarget(this.getThrower(), possibleTarget)){
|
||||
target = possibleTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(target != null && Math.abs(this.motionX) < 1 && Math.abs(this.motionY) < 1 && Math.abs(this.motionZ) < 1){
|
||||
this.addVelocity((target.posX - this.posX)/30, (target.posY + target.height/2 - this.posY)/30, (target.posZ - this.posZ)/30);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 50){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
// Cancels out the slowdown effect in EntityThrowable
|
||||
this.motionX /= 0.99;
|
||||
this.motionY /= 0.99;
|
||||
this.motionZ /= 0.99;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected float getGravityVelocity()
|
||||
{
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,582 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IProjectile;
|
||||
import net.minecraft.entity.monster.EntityEnderman;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.play.server.SPacketChangeGameState;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
/** This class was copied from EntityArrow in the 1.7.10 update as part of the overhaul and major cleanup
|
||||
* of the code for the projectiles. It provides a unifying superclass for all <b>directed</b> projectiles (i.e. not
|
||||
* spherical stuff like snowballs), namely magic missile, ice shard, force arrow, lightning arrow and dart.
|
||||
* All spherical projectiles should extend {@link EntityMagicProjectile}.
|
||||
* <p>
|
||||
* This class handles saving of the damage multiplier and all shared logic. Methods are provided which are
|
||||
* triggered at useful points during the entity update cycle as well as a few getters for various properties.
|
||||
* Override any of these to change the behaviour (no need to call super for any of them).
|
||||
* @since Wizardry 1.0
|
||||
* @author Electroblob
|
||||
*/
|
||||
public abstract class EntityMagicArrow extends Entity implements IProjectile, IEntityAdditionalSpawnData {
|
||||
|
||||
private int blockX = -1;
|
||||
private int blockY = -1;
|
||||
private int blockZ = -1;
|
||||
/** The block the arrow is stuck in */
|
||||
private IBlockState stuckInBlock;
|
||||
/** The metadata of the block the arrow is stuck in */
|
||||
private int inData;
|
||||
private boolean inGround;
|
||||
/** Seems to be some sort of timer for animating an arrow. */
|
||||
public int arrowShake;
|
||||
/** The owner of this arrow. */
|
||||
private WeakReference<EntityLivingBase> shootingEntity;
|
||||
/** The UUID of the caster. Note that this is only for loading purposes; during normal updates
|
||||
* the actual entity instance is stored (so that getEntityByUUID is not called constantly),
|
||||
* so this will not always be synced (this is why it is private). */
|
||||
private UUID casterUUID;
|
||||
int ticksInGround;
|
||||
int ticksInAir;
|
||||
/** The amount of knockback an arrow applies when it hits a mob. */
|
||||
private int knockbackStrength;
|
||||
/** The damage multiplier for the arrow. Normally this isn't set directly, since it can be done via the
|
||||
* constructor. An exception is where other entities need to pass in their multipliers, e.g. ice charge. */
|
||||
public float damageMultiplier = 1.0f;
|
||||
|
||||
/** Basic shell constructor. Should only be used by the client. */
|
||||
public EntityMagicArrow(World world)
|
||||
{
|
||||
super(world);
|
||||
this.setSize(0.5F, 0.5F);
|
||||
}
|
||||
|
||||
/** Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this
|
||||
* constructor and then call setVelocity() as that method is, bizarrely, client-side only. */
|
||||
public EntityMagicArrow(World world, double x, double y, double z)
|
||||
{
|
||||
super(world);
|
||||
this.setSize(0.5F, 0.5F);
|
||||
this.setPosition(x, y, z);
|
||||
// yOffset was set to 0 here, but that has been replaced by getYOffset(), which returns 0 in Entity anyway.
|
||||
}
|
||||
|
||||
/** Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be altered
|
||||
* slightly by a random amount determined by the aimingError parameter. For reference, skeletons set this to 10 on easy, 6 on
|
||||
* normal and 2 on hard difficulty. */
|
||||
public EntityMagicArrow(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, float damageMultiplier)
|
||||
{
|
||||
super(world);
|
||||
this.shootingEntity = new WeakReference<EntityLivingBase>(caster);
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
|
||||
this.posY = caster.posY + (double)caster.getEyeHeight() - 0.10000000149011612D;
|
||||
double d0 = target.posX - caster.posX;
|
||||
double d1 = this.doGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0F) - this.posY
|
||||
: target.getEntityBoundingBox().minY + (double)(target.height / 2.0F) - this.posY;
|
||||
double d2 = target.posZ - caster.posZ;
|
||||
double d3 = (double)MathHelper.sqrt_double(d0 * d0 + d2 * d2);
|
||||
|
||||
if (d3 >= 1.0E-7D)
|
||||
{
|
||||
float f2 = (float)(Math.atan2(d2, d0) * 180.0D / Math.PI) - 90.0F;
|
||||
float f3 = (float)(-(Math.atan2(d1, d3) * 180.0D / Math.PI));
|
||||
double d4 = d0 / d3;
|
||||
double d5 = d2 / d3;
|
||||
this.setLocationAndAngles(caster.posX + d4, this.posY, caster.posZ + d5, f2, f3);
|
||||
// yOffset was set to 0 here, but that has been replaced by getYOffset(), which returns 0 in Entity anyway.
|
||||
|
||||
// f4 depends on the horizontal distance between the two entities and accounts for bullet drop,
|
||||
// but of course if gravity is ignored this should be 0.
|
||||
float bulletDropCompensation = this.doGravity() ? (float)d3 * 0.2F : 0;
|
||||
this.setThrowableHeading(d0, d1 + (double)bulletDropCompensation, d2, speed, aimingError);
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a projectile pointing in the direction the caster is looking, with the given speed.
|
||||
* <b>Use this constructor for normal, player-cast spells. */
|
||||
public EntityMagicArrow(World world, EntityLivingBase caster, float speed, float damageMultiplier)
|
||||
{
|
||||
super(world);
|
||||
this.shootingEntity = new WeakReference<EntityLivingBase>(caster);
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
|
||||
this.setSize(0.5F, 0.5F);
|
||||
this.setLocationAndAngles(caster.posX, caster.posY + (double)caster.getEyeHeight(), caster.posZ, caster.rotationYaw, caster.rotationPitch);
|
||||
this.posX -= (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * 0.16F);
|
||||
this.posY -= 0.10000000149011612D;
|
||||
this.posZ -= (double)(MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * 0.16F);
|
||||
this.setPosition(this.posX, this.posY, this.posZ);
|
||||
// yOffset was set to 0 here, but that has been replaced by getYOffset(), which returns 0 in Entity anyway.
|
||||
this.motionX = (double)(-MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI));
|
||||
this.motionZ = (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI));
|
||||
this.motionY = (double)(-MathHelper.sin(this.rotationPitch / 180.0F * (float)Math.PI));
|
||||
this.setThrowableHeading(this.motionX, this.motionY, this.motionZ, speed * 1.5F, 1.0F);
|
||||
}
|
||||
|
||||
/** Subclasses must override this to set their own base damage. */
|
||||
public abstract double getDamage();
|
||||
|
||||
/** Override this to specify the damage type dealt. Defaults to {@link DamageType#MAGIC}. */
|
||||
public DamageType getDamageType(){
|
||||
return DamageType.MAGIC;
|
||||
}
|
||||
|
||||
/** Override this to disable gravity. Returns true by default. */
|
||||
public boolean doGravity(){
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Override this to disable deceleration (generally speaking, this isn't noticeable unless gravity
|
||||
* is turned off). Returns true by default. */
|
||||
public boolean doDeceleration(){
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Override this to allow the projectile to pass through mobs intact (the onEntityHit method will
|
||||
* still be called and damage will still be applied). Returns false by default. */
|
||||
public boolean doOverpenetration(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to setArrowHeading, it's point the throwable entity to a x, y, z direction.
|
||||
*/
|
||||
public void setThrowableHeading(double x, double y, double z, float speed, float randomness)
|
||||
{
|
||||
float f2 = MathHelper.sqrt_double(x * x + y * y + z * z);
|
||||
x /= (double)f2;
|
||||
y /= (double)f2;
|
||||
z /= (double)f2;
|
||||
x += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)randomness;
|
||||
y += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)randomness;
|
||||
z += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)randomness;
|
||||
x *= (double)speed;
|
||||
y *= (double)speed;
|
||||
z *= (double)speed;
|
||||
this.motionX = x;
|
||||
this.motionY = y;
|
||||
this.motionZ = z;
|
||||
float f3 = MathHelper.sqrt_double(x * x + z * z);
|
||||
this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(x, z) * 180.0D / Math.PI);
|
||||
this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(y, (double)f3) * 180.0D / Math.PI);
|
||||
this.ticksInGround = 0;
|
||||
}
|
||||
|
||||
// There was an override for setPositionAndRotationDirect here, but it was exactly the same as the superclass
|
||||
// method (in Entity), so it was removed since it was redundant.
|
||||
|
||||
/**
|
||||
* Sets the velocity to the args. Args: x, y, z. THIS IS CLIENT SIDE ONLY! DO NOT USE IN COMMON OR SERVER CODE!
|
||||
*/
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void setVelocity(double p_70016_1_, double p_70016_3_, double p_70016_5_)
|
||||
{
|
||||
this.motionX = p_70016_1_;
|
||||
this.motionY = p_70016_3_;
|
||||
this.motionZ = p_70016_5_;
|
||||
|
||||
if (this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F)
|
||||
{
|
||||
float f = MathHelper.sqrt_double(p_70016_1_ * p_70016_1_ + p_70016_5_ * p_70016_5_);
|
||||
this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(p_70016_1_, p_70016_5_) * 180.0D / Math.PI);
|
||||
this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(p_70016_3_, (double)f) * 180.0D / Math.PI);
|
||||
this.prevRotationPitch = this.rotationPitch;
|
||||
this.prevRotationYaw = this.rotationYaw;
|
||||
this.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, this.rotationPitch);
|
||||
this.ticksInGround = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Called each tick when the projectile is in a block. Defaults to setDead(), but can be overridden to
|
||||
* change the behaviour. */
|
||||
public void tickInGround(){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
/** Called each tick when the projectile is in the air. Override to add particles and such like. */
|
||||
public void tickInAir(){}
|
||||
|
||||
/** Called when the projectile hits an entity. Override to add potion effects and such like. */
|
||||
public void onEntityHit(EntityLivingBase entityHit){}
|
||||
|
||||
/** Called when the projectile hits a block. Override to add sound effects and such like. */
|
||||
public void onBlockHit(){}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(this.getShootingEntity() == null && this.casterUUID != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(worldObj, casterUUID);
|
||||
if(entity instanceof EntityLivingBase){
|
||||
this.shootingEntity = new WeakReference<EntityLivingBase>((EntityLivingBase)entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F)
|
||||
{
|
||||
float f = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
|
||||
this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI);
|
||||
this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(this.motionY, (double)f) * 180.0D / Math.PI);
|
||||
}
|
||||
|
||||
BlockPos blockpos = new BlockPos(this.blockX, this.blockY, this.blockZ);
|
||||
IBlockState iblockstate = this.worldObj.getBlockState(blockpos);
|
||||
|
||||
if (iblockstate.getMaterial() != Material.AIR)
|
||||
{
|
||||
AxisAlignedBB axisalignedbb = iblockstate.getCollisionBoundingBox(this.worldObj, blockpos);
|
||||
|
||||
if (axisalignedbb != Block.NULL_AABB && axisalignedbb.offset(blockpos).isVecInside(new Vec3d(this.posX, this.posY, this.posZ)))
|
||||
{
|
||||
this.inGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.arrowShake > 0)
|
||||
{
|
||||
--this.arrowShake;
|
||||
}
|
||||
|
||||
// When the arrow is in the ground
|
||||
if (this.inGround)
|
||||
{
|
||||
++this.ticksInGround;
|
||||
this.tickInGround();
|
||||
}
|
||||
// When the arrow is in the air
|
||||
else{
|
||||
|
||||
this.tickInAir();
|
||||
|
||||
this.ticksInGround = 0;
|
||||
++this.ticksInAir;
|
||||
Vec3d vec3d1 = new Vec3d(this.posX, this.posY, this.posZ);
|
||||
Vec3d vec3d = new Vec3d(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);
|
||||
RayTraceResult raytraceresult = this.worldObj.rayTraceBlocks(vec3d1, vec3d, false, true, false);
|
||||
vec3d1 = new Vec3d(this.posX, this.posY, this.posZ);
|
||||
vec3d = new Vec3d(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);
|
||||
|
||||
if (raytraceresult != null)
|
||||
{
|
||||
vec3d = new Vec3d(raytraceresult.hitVec.xCoord, raytraceresult.hitVec.yCoord, raytraceresult.hitVec.zCoord);
|
||||
}
|
||||
|
||||
|
||||
Entity entity = null;
|
||||
List<?> list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox().addCoord(this.motionX, this.motionY, this.motionZ).expand(1.0D, 1.0D, 1.0D));
|
||||
double d0 = 0.0D;
|
||||
int i;
|
||||
float f1;
|
||||
|
||||
for (i = 0; i < list.size(); ++i)
|
||||
{
|
||||
Entity entity1 = (Entity)list.get(i);
|
||||
|
||||
if (entity1.canBeCollidedWith() && (entity1 != this.getShootingEntity() || this.ticksInAir >= 5))
|
||||
{
|
||||
f1 = 0.3F;
|
||||
AxisAlignedBB axisalignedbb1 = entity1.getEntityBoundingBox().expand((double)f1, (double)f1, (double)f1);
|
||||
RayTraceResult RayTraceResult1 = axisalignedbb1.calculateIntercept(vec3d1, vec3d);
|
||||
|
||||
if (RayTraceResult1 != null)
|
||||
{
|
||||
double d1 = vec3d1.distanceTo(RayTraceResult1.hitVec);
|
||||
|
||||
if (d1 < d0 || d0 == 0.0D)
|
||||
{
|
||||
entity = entity1;
|
||||
d0 = d1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entity != null)
|
||||
{
|
||||
raytraceresult = new RayTraceResult(entity);
|
||||
}
|
||||
|
||||
// Players that are considered invulnerable to the caster allow the projectile to pass straight through them.
|
||||
if (raytraceresult != null && raytraceresult.entityHit != null && raytraceresult.entityHit instanceof EntityPlayer)
|
||||
{
|
||||
EntityPlayer entityplayer = (EntityPlayer)raytraceresult.entityHit;
|
||||
|
||||
if (entityplayer.capabilities.disableDamage || this.getShootingEntity() instanceof EntityPlayer && !((EntityPlayer)this.getShootingEntity()).canAttackPlayer(entityplayer))
|
||||
{
|
||||
raytraceresult = null;
|
||||
}
|
||||
}
|
||||
|
||||
// If the arrow hits something
|
||||
if (raytraceresult != null)
|
||||
{
|
||||
// If the arrow hits an entity
|
||||
if (raytraceresult.entityHit != null)
|
||||
{
|
||||
DamageSource damagesource = null;
|
||||
|
||||
if (this.getShootingEntity() == null)
|
||||
{
|
||||
damagesource = DamageSource.causeThrownDamage(this, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
damagesource = MagicDamage.causeIndirectMagicDamage(this, (EntityLivingBase)this.getShootingEntity(), this.getDamageType()).setProjectile();
|
||||
}
|
||||
|
||||
if (raytraceresult.entityHit.attackEntityFrom(damagesource, (float)(this.getDamage()*this.damageMultiplier)))
|
||||
{
|
||||
if (raytraceresult.entityHit instanceof EntityLivingBase)
|
||||
{
|
||||
EntityLivingBase entityHit = (EntityLivingBase)raytraceresult.entityHit;
|
||||
|
||||
this.onEntityHit(entityHit);
|
||||
|
||||
if (this.knockbackStrength > 0)
|
||||
{
|
||||
float f4 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
|
||||
|
||||
if (f4 > 0.0F)
|
||||
{
|
||||
raytraceresult.entityHit.addVelocity(this.motionX * (double)this.knockbackStrength * 0.6000000238418579D / (double)f4, 0.1D, this.motionZ * (double)this.knockbackStrength * 0.6000000238418579D / (double)f4);
|
||||
}
|
||||
}
|
||||
|
||||
// Thorns enchantment
|
||||
if (this.getShootingEntity() != null && this.getShootingEntity() instanceof EntityLivingBase)
|
||||
{
|
||||
EnchantmentHelper.applyThornEnchantments(entityHit, this.getShootingEntity());
|
||||
EnchantmentHelper.applyArthropodEnchantments((EntityLivingBase)this.getShootingEntity(), entityHit);
|
||||
}
|
||||
|
||||
if (this.getShootingEntity() != null && raytraceresult.entityHit != this.getShootingEntity() && raytraceresult.entityHit instanceof EntityPlayer && this.getShootingEntity() instanceof EntityPlayerMP)
|
||||
{
|
||||
((EntityPlayerMP)this.getShootingEntity()).connection.sendPacket(new SPacketChangeGameState(6, 0.0F));
|
||||
}
|
||||
}
|
||||
|
||||
if (!(raytraceresult.entityHit instanceof EntityEnderman) && !this.doOverpenetration())
|
||||
{
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!this.doOverpenetration()) this.setDead();
|
||||
|
||||
// Was the 'rebound' that happened when entities were immune to damage
|
||||
/*
|
||||
this.motionX *= -0.10000000149011612D;
|
||||
this.motionY *= -0.10000000149011612D;
|
||||
this.motionZ *= -0.10000000149011612D;
|
||||
this.rotationYaw += 180.0F;
|
||||
this.prevRotationYaw += 180.0F;
|
||||
this.ticksInAir = 0;
|
||||
*/
|
||||
}
|
||||
}
|
||||
// If the arrow hits a block
|
||||
else
|
||||
{
|
||||
this.blockX = raytraceresult.getBlockPos().getX();
|
||||
this.blockY = raytraceresult.getBlockPos().getY();
|
||||
this.blockZ = raytraceresult.getBlockPos().getZ();
|
||||
this.stuckInBlock = this.worldObj.getBlockState(raytraceresult.getBlockPos());
|
||||
this.motionX = (double)((float)(raytraceresult.hitVec.xCoord - this.posX));
|
||||
this.motionY = (double)((float)(raytraceresult.hitVec.yCoord - this.posY));
|
||||
this.motionZ = (double)((float)(raytraceresult.hitVec.zCoord - this.posZ));
|
||||
//f2 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ);
|
||||
//this.posX -= this.motionX / (double)f2 * 0.05000000074505806D;
|
||||
//this.posY -= this.motionY / (double)f2 * 0.05000000074505806D;
|
||||
//this.posZ -= this.motionZ / (double)f2 * 0.05000000074505806D;
|
||||
//this.playSound("random.bowhit", 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
this.inGround = true;
|
||||
this.arrowShake = 7;
|
||||
|
||||
this.onBlockHit();
|
||||
|
||||
if (this.stuckInBlock.getMaterial() != Material.AIR)
|
||||
{
|
||||
this.stuckInBlock.getBlock().onEntityCollidedWithBlock(this.worldObj, raytraceresult.getBlockPos(), this.stuckInBlock, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.posX += this.motionX;
|
||||
this.posY += this.motionY;
|
||||
this.posZ += this.motionZ;
|
||||
//f2 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
|
||||
this.rotationYaw = (float)(Math.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI);
|
||||
|
||||
//for (this.rotationPitch = (float)(Math.atan2(this.motionY, (double)f2) * 180.0D / Math.PI); this.rotationPitch - this.prevRotationPitch < -180.0F; this.prevRotationPitch -= 360.0F)
|
||||
//{
|
||||
// ;
|
||||
//}
|
||||
|
||||
while (this.rotationPitch - this.prevRotationPitch >= 180.0F)
|
||||
{
|
||||
this.prevRotationPitch += 360.0F;
|
||||
}
|
||||
|
||||
while (this.rotationYaw - this.prevRotationYaw < -180.0F)
|
||||
{
|
||||
this.prevRotationYaw -= 360.0F;
|
||||
}
|
||||
|
||||
while (this.rotationYaw - this.prevRotationYaw >= 180.0F)
|
||||
{
|
||||
this.prevRotationYaw += 360.0F;
|
||||
}
|
||||
|
||||
this.rotationPitch = this.prevRotationPitch + (this.rotationPitch - this.prevRotationPitch) * 0.2F;
|
||||
this.rotationYaw = this.prevRotationYaw + (this.rotationYaw - this.prevRotationYaw) * 0.2F;
|
||||
|
||||
float f3 = 0.99F;
|
||||
|
||||
if (this.isInWater())
|
||||
{
|
||||
for (int l = 0; l < 4; ++l)
|
||||
{
|
||||
float f4 = 0.25F;
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.WATER_BUBBLE, this.posX - this.motionX * (double)f4, this.posY - this.motionY * (double)f4, this.posZ - this.motionZ * (double)f4, this.motionX, this.motionY, this.motionZ);
|
||||
}
|
||||
|
||||
f3 = 0.8F;
|
||||
}
|
||||
|
||||
if (this.isWet())
|
||||
{
|
||||
this.extinguish();
|
||||
}
|
||||
|
||||
if(this.doDeceleration()){
|
||||
this.motionX *= (double)f3;
|
||||
this.motionY *= (double)f3;
|
||||
this.motionZ *= (double)f3;
|
||||
}
|
||||
|
||||
if(this.doGravity()) this.motionY -= 0.05;
|
||||
|
||||
this.setPosition(this.posX, this.posY, this.posZ);
|
||||
this.doBlockCollisions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound tag)
|
||||
{
|
||||
tag.setShort("xTile", (short)this.blockX);
|
||||
tag.setShort("yTile", (short)this.blockY);
|
||||
tag.setShort("zTile", (short)this.blockZ);
|
||||
tag.setShort("life", (short)this.ticksInGround);
|
||||
if(this.stuckInBlock != null){
|
||||
ResourceLocation resourcelocation = (ResourceLocation)Block.REGISTRY.getNameForObject(this.stuckInBlock.getBlock());
|
||||
tag.setString("inTile", resourcelocation == null ? "" : resourcelocation.toString());
|
||||
}
|
||||
tag.setByte("inData", (byte)this.inData);
|
||||
tag.setByte("shake", (byte)this.arrowShake);
|
||||
tag.setByte("inGround", (byte)(this.inGround ? 1 : 0));
|
||||
tag.setFloat("damageMultiplier", this.damageMultiplier);
|
||||
if(this.getShootingEntity() != null){
|
||||
tag.setUniqueId("casterUUID", this.getShootingEntity().getUniqueID());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound tag)
|
||||
{
|
||||
this.blockX = tag.getShort("xTile");
|
||||
this.blockY = tag.getShort("yTile");
|
||||
this.blockZ = tag.getShort("zTile");
|
||||
this.ticksInGround = tag.getShort("life");
|
||||
// Commented out for now because there's some funny stuff going on with blockstates and metadata.
|
||||
//this.stuckInBlock = Block.getBlockById(tag.getByte("inTile") & 255);
|
||||
this.inData = tag.getByte("inData") & 255;
|
||||
this.arrowShake = tag.getByte("shake") & 255;
|
||||
this.inGround = tag.getByte("inGround") == 1;
|
||||
this.damageMultiplier = tag.getFloat("damageMultiplier");
|
||||
casterUUID = tag.getUniqueId("casterUUID");
|
||||
}
|
||||
|
||||
/**
|
||||
* returns if this entity triggers Block.onEntityWalking on the blocks they walk on. used for spiders and wolves to
|
||||
* prevent them from trampling crops
|
||||
*/
|
||||
protected boolean canTriggerWalking()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public float getShadowSize()
|
||||
{
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount of knockback the arrow applies when it hits a mob.
|
||||
*/
|
||||
public void setKnockbackStrength(int p_70240_1_)
|
||||
{
|
||||
this.knockbackStrength = p_70240_1_;
|
||||
}
|
||||
|
||||
/**
|
||||
* If returns false, the item will not inflict any damage against entities.
|
||||
*/
|
||||
public boolean canAttackWithItem()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void writeSpawnData(ByteBuf buffer){
|
||||
if(this.getShootingEntity() != null) buffer.writeInt(this.getShootingEntity().getEntityId());
|
||||
}
|
||||
|
||||
public void readSpawnData(ByteBuf buffer){
|
||||
if(buffer.isReadable()) this.shootingEntity = new WeakReference<EntityLivingBase>((EntityLivingBase)this.worldObj.getEntityByID(buffer.readInt()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the EntityLivingBase that created this construct, or null if it no longer exists. Cases where the
|
||||
* entity may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported
|
||||
* to another dimension, or this construct simply had no caster in the first place.
|
||||
*/
|
||||
public EntityLivingBase getShootingEntity() {
|
||||
return shootingEntity == null ? null : shootingEntity.get();
|
||||
}
|
||||
|
||||
public void setShootingEntity(EntityLivingBase entity) {
|
||||
shootingEntity = new WeakReference<EntityLivingBase>(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityMagicMissile extends EntityMagicArrow {
|
||||
|
||||
/** Basic shell constructor. Should only be used by the client. */
|
||||
public EntityMagicMissile(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
/** Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this
|
||||
* constructor and then call setVelocity() as that method is, bizarrely, client-side only. */
|
||||
public EntityMagicMissile(World world, double x, double y, double z)
|
||||
{
|
||||
super(world, x, y, z);
|
||||
}
|
||||
|
||||
/** Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be altered
|
||||
* slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on easy, 6 on
|
||||
* normal and 2 on hard difficulty. */
|
||||
public EntityMagicMissile(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, target, speed, aimingError, damageMultiplier);
|
||||
}
|
||||
|
||||
/** Creates a projectile pointing in the direction the caster is looking, with the given speed.
|
||||
* USE THIS CONSTRUCTOR FOR NORMAL SPELLS. */
|
||||
public EntityMagicMissile(World world, EntityLivingBase caster, float speed, float damageMultiplier)
|
||||
{
|
||||
super(world, caster, speed, damageMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
|
||||
if (this.ticksExisted > 20){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
if(this.worldObj.isRemote){
|
||||
|
||||
if(this.ticksExisted % 2 == 1){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX, this.posY, this.posZ, 0, 0, 0, 20 + rand.nextInt(10), 0.5f + (rand.nextFloat()/2), 0.5f + (rand.nextFloat()/2), 0.5f + (rand.nextFloat()/2));
|
||||
}
|
||||
else{
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX, this.posY, this.posZ, 0, 0, 0, 20 + rand.nextInt(10), 0.5f + (rand.nextFloat()/2), 0.5f + (rand.nextFloat()/2), 0.5f + (rand.nextFloat()/2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDamage(){
|
||||
return 4.0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGravity(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDeceleration(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.projectile.EntityThrowable;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
/** This class is a generic superclass for all <b>non-directed</b> projectiles, namely: darkness orb, firebolt, firebomb,
|
||||
* force orb, ice charge, lightning disc, poison bomb, spark, spark bomb and thunderbolt. Directed (arrow-like) projectiles
|
||||
* should instead extend {@link EntityMagicArrow}.
|
||||
* <p>
|
||||
* This class purely handles saving of the damage multiplier; EntityThrowable is pretty well suited to my purposes
|
||||
* as it is. Range is done via the velocity when the constructor is called. Caster is already handled by EntityThrowable.getThrower().
|
||||
*
|
||||
* Note that this class does not implement {@link IEntityAdditionalSpawnData}; subclasses that need to transfer extra data
|
||||
* to the client should implement that interface themselves. See {@link EntityBomb} for an example.
|
||||
* @since Wizardry 1.0
|
||||
* @author Electroblob
|
||||
* @see EntityBomb
|
||||
*/
|
||||
public abstract class EntityMagicProjectile extends EntityThrowable {
|
||||
|
||||
public float damageMultiplier = 1.0f;
|
||||
|
||||
public EntityMagicProjectile(World world)
|
||||
{
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityMagicProjectile(World world, EntityLivingBase thrower)
|
||||
{
|
||||
super(world, thrower);
|
||||
}
|
||||
|
||||
public EntityMagicProjectile(World world, EntityLivingBase thrower, float damageMultiplier)
|
||||
{
|
||||
super(world, thrower);
|
||||
// This is the standard set of parameters for this method, used by snowballs and ender pearls amongst others.
|
||||
this.setHeadingFromThrower(thrower, thrower.rotationPitch, thrower.rotationYaw, 0.0f, this.getSpeed(), 1.0f);
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
}
|
||||
|
||||
public EntityMagicProjectile(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
/** This got removed at some point since 1.7.10, but I liked it so I thought I'd add it back in again. */
|
||||
protected float getSpeed(){
|
||||
return 1.5f;
|
||||
}
|
||||
|
||||
/** Sets this projectile's velocity as a normalised vector towards the target. */
|
||||
public void directTowards(Entity target, float velocity){
|
||||
|
||||
double dx = target.posX - this.posX;
|
||||
double dy = target.getEntityBoundingBox().minY + (double)(target.height / 2.0F) - (this.posY + (double)(this.height / 2.0F));
|
||||
double dz = target.posZ - this.posZ;
|
||||
|
||||
this.motionX = dx/this.getDistanceToEntity(target) * velocity;
|
||||
this.motionY = dy/this.getDistanceToEntity(target) * velocity;
|
||||
this.motionZ = dz/this.getDistanceToEntity(target) * velocity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
damageMultiplier = nbttagcompound.getFloat("damageMultiplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setFloat("damageMultiplier", damageMultiplier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityPoisonBomb extends EntityBomb {
|
||||
|
||||
public EntityPoisonBomb(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityPoisonBomb(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityPoisonBomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, float blastMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier);
|
||||
}
|
||||
|
||||
public EntityPoisonBomb(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult)
|
||||
{
|
||||
Entity entityHit = par1RayTraceResult.entityHit;
|
||||
|
||||
if (entityHit != null)
|
||||
{
|
||||
// This is if the poison bomb gets a direct hit
|
||||
float damage = 5 * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.POISON).setProjectile(), damage);
|
||||
|
||||
if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.POISON, entityHit)) ((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(MobEffects.POISON, 120, 1));
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0;i<60*blastMultiplier;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0.0d, 0.0d, 0.0d, 35, 0.2f + rand.nextFloat()*0.3f, 0.6f, 0.0f);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0.0d, 0.0d, 0.0d, 0, 0.2f + rand.nextFloat()*0.2f, 0.8f, 0.0f);
|
||||
}
|
||||
// Spawning this after the other particles fixes the rendering colour bug. It's a bit of a cheat, but it works pretty well.
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
}
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
|
||||
this.playSound(SoundEvents.BLOCK_FIRE_EXTINGUISH, 1.2F, 1.0f);
|
||||
|
||||
double range = 3.0d*blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
if(target != entityHit && target != this.getThrower() && !MagicDamage.isEntityImmune(DamageType.POISON, target)){
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.POISON), 4.0f * damageMultiplier);
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.POISON, 100, 1));
|
||||
}
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySmokeBomb extends EntityBomb {
|
||||
|
||||
public EntitySmokeBomb(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntitySmokeBomb(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntitySmokeBomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, float blastMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier);
|
||||
}
|
||||
|
||||
public EntitySmokeBomb(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult){
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
for(int i=0;i<60*blastMultiplier;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0, 0, 0);
|
||||
float brightness = rand.nextFloat() * 0.3f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0.0d, 0.0d, 0.0d, 0, brightness, brightness, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
|
||||
this.playSound(SoundEvents.BLOCK_FIRE_EXTINGUISH, 1.2F, 1.0f);
|
||||
|
||||
double range = 3.0d * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
if(target != this.getThrower()){
|
||||
// Gives the target blindness if it is a player, mind trick otherwise (since this has the desired
|
||||
// effect of preventing targeting)
|
||||
if(target instanceof EntityPlayer){
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 120, 0));
|
||||
}else if(target instanceof EntityLiving){
|
||||
// New AI
|
||||
((EntityLiving)target).setAttackTarget(null);
|
||||
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, 120, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySpark extends EntityMagicProjectile {
|
||||
|
||||
public EntitySpark(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntitySpark(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntitySpark(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier);
|
||||
}
|
||||
|
||||
public EntitySpark(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
/** This is the speed */
|
||||
protected float getSpeed()
|
||||
{
|
||||
return 0.5F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this EntityThrowable hits a block or entity.
|
||||
*/
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult)
|
||||
{
|
||||
Entity entityHit = par1RayTraceResult.entityHit;
|
||||
|
||||
if (entityHit != null){
|
||||
|
||||
float damage = 6 * damageMultiplier;
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK), damage);
|
||||
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_SPARK, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0;i<8;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + rand.nextFloat() - 0.5, this.posY + this.height/2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.isCollided && !worldObj.isRemote){
|
||||
|
||||
double seekingRange = 5.0d;
|
||||
|
||||
List<EntityLivingBase> entities = WizardryUtilities.getEntitiesWithinRadius(seekingRange, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
Entity target = null;
|
||||
|
||||
for(Entity possibleTarget : entities){
|
||||
// Decides if current entity should be replaced.
|
||||
if(target == null || this.getDistanceToEntity(target) > this.getDistanceToEntity(possibleTarget)){
|
||||
// Decides if new entity is a valid target.
|
||||
if(WizardryUtilities.isValidTarget(this.getThrower(), possibleTarget)){
|
||||
target = possibleTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(target != null && Math.abs(this.motionX) < 1 && Math.abs(this.motionY) < 1 && Math.abs(this.motionZ) < 1){
|
||||
this.addVelocity((target.posX - this.posX)/30, (target.posY + target.height/2 - this.posY)/30, (target.posZ - this.posZ)/30);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 100){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of gravity to apply to the thrown entity with each tick.
|
||||
*/
|
||||
protected float getGravityVelocity()
|
||||
{
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.entity.EntityArc;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySparkBomb extends EntityBomb {
|
||||
|
||||
/** For client use, because thrower field is not visible. */
|
||||
private int casterID;
|
||||
|
||||
public EntitySparkBomb(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntitySparkBomb(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntitySparkBomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, float blastMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier);
|
||||
}
|
||||
|
||||
public EntitySparkBomb(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this EntityThrowable hits a block or entity.
|
||||
*/
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult)
|
||||
{
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST_FAR, 0.5f, 0.5f);
|
||||
|
||||
Entity entityHit = par1RayTraceResult.entityHit;
|
||||
|
||||
if (entityHit != null)
|
||||
{
|
||||
// This is if the spark bomb gets a direct hit
|
||||
float damage = 6 * damageMultiplier;
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(), damage);
|
||||
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0;i<8;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + rand.nextFloat() - 0.5, this.posY + this.height/2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0, 3);
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + rand.nextFloat() - 0.5, this.posY + this.height/2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
double seekerRange = 5.0d * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, this.posY, this.posZ, this.worldObj);
|
||||
|
||||
for(int i=0; i<Math.min(targets.size(), 4); i++){
|
||||
|
||||
boolean flag = targets.get(i) != entityHit && targets.get(i) != this.getThrower() && !(targets.get(i) instanceof EntityPlayer && ((EntityPlayer)targets.get(i)).capabilities.isCreativeMode);
|
||||
|
||||
// Detects (client side) if target is the thrower, to stop particles being spawned around them.
|
||||
if(flag && worldObj.isRemote && targets.get(i).getEntityId() == this.casterID) flag = false;
|
||||
|
||||
if(flag){
|
||||
|
||||
EntityLivingBase target = targets.get(i);
|
||||
|
||||
if(!this.worldObj.isRemote){
|
||||
|
||||
EntityArc arc = new EntityArc(this.worldObj);
|
||||
arc.setEndpointCoords(this.posX, this.posY, this.posZ,
|
||||
target.posX, target.posY + target.height/2, target.posZ);
|
||||
this.worldObj.spawnEntityInWorld(arc);
|
||||
|
||||
target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, rand.nextFloat() * 0.4F + 1.5F);
|
||||
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK), 5.0f * damageMultiplier);
|
||||
|
||||
}else{
|
||||
// Particle effect
|
||||
for(int j=0;j<8;j++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, target.posX + rand.nextFloat() - 0.5, target.getEntityBoundingBox().minY + target.height*rand.nextFloat(), target.posZ + rand.nextFloat() - 0.5, 0, 0, 0, 3);
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, target.posX + rand.nextFloat() - 0.5, target.getEntityBoundingBox().minY + target.height*rand.nextFloat(), target.posZ + rand.nextFloat() - 0.5, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
super.writeSpawnData(data);
|
||||
data.writeInt(this.getThrower().getEntityId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
super.readSpawnData(data);
|
||||
this.casterID = data.readInt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityThunderbolt extends EntityMagicProjectile {
|
||||
|
||||
public EntityThunderbolt(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
public EntityThunderbolt(World par1World, EntityLivingBase par2EntityLivingBase)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase);
|
||||
}
|
||||
|
||||
public EntityThunderbolt(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier)
|
||||
{
|
||||
super(par1World, par2EntityLivingBase, damageMultiplier);
|
||||
}
|
||||
|
||||
public EntityThunderbolt(World par1World, double par2, double par4, double par6)
|
||||
{
|
||||
super(par1World, par2, par4, par6);
|
||||
}
|
||||
|
||||
/** This is the speed */
|
||||
protected float getSpeed()
|
||||
{
|
||||
return 2.5F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this EntityThrowable hits a block or entity.
|
||||
*/
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult)
|
||||
{
|
||||
Entity entityHit = par1RayTraceResult.entityHit;
|
||||
|
||||
if(entityHit != null){
|
||||
|
||||
float damage = 3 * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(), damage);
|
||||
|
||||
// Knockback
|
||||
entityHit.addVelocity(this.motionX*0.2, this.motionY*0.2, this.motionZ*0.2);
|
||||
}
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_LARGE_BLAST, 1.4F, 0.5f + this.rand.nextFloat() * 0.1F);
|
||||
|
||||
// Particle effect
|
||||
if(worldObj.isRemote){
|
||||
worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + rand.nextFloat()*0.2 - 0.1, this.posY + this.height/2 + rand.nextFloat()*0.2 - 0.1, this.posZ + rand.nextFloat()*0.2 - 0.1, 0, 0, 0, 3);
|
||||
for(int i=0; i<4; i++){
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, this.posX + rand.nextFloat()*0.2 - 0.1, this.posY + this.height/2 + rand.nextFloat()*0.2 - 0.1, this.posZ + rand.nextFloat()*0.2 - 0.1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 8){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of gravity to apply to the thrown entity with each tick.
|
||||
*/
|
||||
protected float getGravityVelocity()
|
||||
{
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity should be rendered as on fire.
|
||||
*/
|
||||
public boolean canRenderOnFire()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user