Initial commit

This commit is contained in:
Electroblob
2018-01-19 20:33:04 +00:00
commit da6b007a3a
945 changed files with 60616 additions and 0 deletions
@@ -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;
}
}