That's one heck of a commit you've got there...
I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
package electroblob.wizardry.entity;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.AllyDesignationSystem;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockFalling;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.MoverType;
|
||||
import net.minecraft.entity.item.EntityFallingBlock;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Custom extended version of {@link EntityFallingBlock} for use with the greater telekinesis spell. */
|
||||
public class EntityLevitatingBlock extends EntityFallingBlock implements IEntityAdditionalSpawnData {
|
||||
|
||||
private static final Field fallTile;
|
||||
|
||||
static {
|
||||
fallTile = ObfuscationReflectionHelper.findField(EntityFallingBlock.class, "field_175132_d");
|
||||
fallTile.setAccessible(true);
|
||||
}
|
||||
|
||||
/** The entity that created this levitating block */
|
||||
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 damage multiplier for this levitating block, determined by the wand with which it was cast. */
|
||||
public float damageMultiplier = 1.0f;
|
||||
|
||||
private int suspendTimer = 5;
|
||||
|
||||
public EntityLevitatingBlock(World world){
|
||||
super(world);
|
||||
// EntityFallingBlock never uses this constructor so doesn't bother setting this, but we need to
|
||||
this.setSize(0.98F, 0.98F);
|
||||
}
|
||||
|
||||
public EntityLevitatingBlock(World world, double x, double y, double z, IBlockState state){
|
||||
super(world, x, y, z, state);
|
||||
}
|
||||
|
||||
/** Resets the suspension timer to 5 ticks, during which this block will not re-attach itself to the ground. */
|
||||
public void suspend(){
|
||||
suspendTimer = 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
if(suspendTimer > 0){
|
||||
suspendTimer--;
|
||||
}
|
||||
|
||||
if(this.getCaster() == null && this.casterUUID != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID);
|
||||
if(entity instanceof EntityLivingBase){
|
||||
this.caster = new WeakReference<>((EntityLivingBase)entity);
|
||||
}
|
||||
}
|
||||
|
||||
if(getBlock() != null){
|
||||
|
||||
// === Copied from super ===
|
||||
|
||||
Block block = getBlock().getBlock();
|
||||
|
||||
if(getBlock().getMaterial() == Material.AIR){
|
||||
this.setDead();
|
||||
|
||||
}else{
|
||||
|
||||
this.prevPosX = this.posX;
|
||||
this.prevPosY = this.posY;
|
||||
this.prevPosZ = this.posZ;
|
||||
|
||||
if(this.fallTime++ == 0){
|
||||
|
||||
BlockPos blockpos = new BlockPos(this);
|
||||
|
||||
if(this.world.getBlockState(blockpos).getBlock() == block){
|
||||
this.world.setBlockToAir(blockpos);
|
||||
}else if(!this.world.isRemote){
|
||||
this.setDead();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.hasNoGravity()){
|
||||
this.motionY -= 0.03999999910593033D;
|
||||
}
|
||||
|
||||
this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ);
|
||||
|
||||
if(!this.world.isRemote){
|
||||
|
||||
BlockPos blockpos1 = new BlockPos(this);
|
||||
boolean isConcrete = getBlock().getBlock() == Blocks.CONCRETE_POWDER;
|
||||
boolean isConcreteInWater = isConcrete && this.world.getBlockState(blockpos1).getMaterial() == Material.WATER;
|
||||
double d0 = this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ;
|
||||
|
||||
if(isConcrete && d0 > 1.0D){
|
||||
|
||||
RayTraceResult raytraceresult = this.world.rayTraceBlocks(new Vec3d(this.prevPosX, this.prevPosY, this.prevPosZ), new Vec3d(this.posX, this.posY, this.posZ), true);
|
||||
|
||||
if(raytraceresult != null && this.world.getBlockState(raytraceresult.getBlockPos()).getMaterial() == Material.WATER){
|
||||
blockpos1 = raytraceresult.getBlockPos();
|
||||
isConcreteInWater = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.onGround && !isConcreteInWater){
|
||||
|
||||
if(this.fallTime > 100 && !this.world.isRemote && (blockpos1.getY() < 1 || blockpos1.getY() > 256) || this.fallTime > 600){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
IBlockState iblockstate = this.world.getBlockState(blockpos1);
|
||||
|
||||
if(this.world.isAirBlock(new BlockPos(this.posX, this.posY - 0.009999999776482582D, this.posZ))){
|
||||
if(!isConcreteInWater && BlockFalling.canFallThrough(this.world.getBlockState(new BlockPos(this.posX, this.posY - 0.009999999776482582D, this.posZ)))){
|
||||
this.onGround = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.motionX *= 0.699999988079071D;
|
||||
this.motionZ *= 0.699999988079071D;
|
||||
this.motionY *= -0.5D;
|
||||
|
||||
if(iblockstate.getBlock() != Blocks.PISTON_EXTENSION){
|
||||
|
||||
if(suspendTimer == 0){
|
||||
|
||||
this.setDead(); // Moved inside the above if statement
|
||||
|
||||
if(this.world.mayPlace(block, blockpos1, true, EnumFacing.UP, null)
|
||||
&& (isConcreteInWater || !BlockFalling.canFallThrough(this.world.getBlockState(blockpos1.down())))
|
||||
&& this.world.setBlockState(blockpos1, getBlock(), 3)){
|
||||
|
||||
if(block instanceof BlockFalling){
|
||||
((BlockFalling)block).onEndFalling(this.world, blockpos1, getBlock(), iblockstate);
|
||||
}
|
||||
|
||||
if(this.tileEntityData != null && block.hasTileEntity(getBlock())){
|
||||
|
||||
TileEntity tileentity = this.world.getTileEntity(blockpos1);
|
||||
|
||||
if(tileentity != null){
|
||||
|
||||
NBTTagCompound nbttagcompound = tileentity.writeToNBT(new NBTTagCompound());
|
||||
|
||||
for(String s : this.tileEntityData.getKeySet()){
|
||||
NBTBase nbtbase = this.tileEntityData.getTag(s);
|
||||
|
||||
if(!"x".equals(s) && !"y".equals(s) && !"z".equals(s)){
|
||||
nbttagcompound.setTag(s, nbtbase.copy());
|
||||
}
|
||||
}
|
||||
|
||||
tileentity.readFromNBT(nbttagcompound);
|
||||
tileentity.markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
// Never drops the block, instead if it can't reattach to the world it breaks
|
||||
world.playEvent(2001, this.getPosition(), Block.getStateId(getBlock()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.motionX *= 0.9800000190734863D;
|
||||
this.motionY *= 0.9800000190734863D;
|
||||
this.motionZ *= 0.9800000190734863D;
|
||||
}
|
||||
|
||||
// === End super copy ===
|
||||
}
|
||||
|
||||
double velocitySquared = motionX * motionX + motionY * motionY + motionZ * motionZ;
|
||||
|
||||
if(velocitySquared >= 0.2){
|
||||
|
||||
List<Entity> list = this.world.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox());
|
||||
|
||||
for(Entity entity : list){
|
||||
|
||||
if(entity instanceof EntityLivingBase && isValidTarget(entity)){
|
||||
|
||||
float damage = Spells.greater_telekinesis.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier;
|
||||
damage *= Math.min(1, velocitySquared/0.4); // Reduce damage at low speeds
|
||||
|
||||
entity.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(),
|
||||
MagicDamage.DamageType.FORCE), damage);
|
||||
|
||||
double dx = -this.motionX;
|
||||
double dz;
|
||||
for(dz = -this.motionZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random()) * 0.01D){
|
||||
dx = (Math.random() - Math.random()) * 0.01D;
|
||||
}
|
||||
((EntityLivingBase)entity).knockBack(this, 0.6f, dx, dz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
public void setCaster(EntityLivingBase caster){
|
||||
if(getCaster() != caster) this.caster = new WeakReference<>(caster);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for {@link AllyDesignationSystem#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 AllyDesignationSystem.isValidTarget(this.getCaster(), target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
casterUUID = nbttagcompound.getUniqueId("casterUUID");
|
||||
damageMultiplier = nbttagcompound.getFloat("damageMultiplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
if(this.getCaster() != null){
|
||||
nbttagcompound.setUniqueId("casterUUID", this.getCaster().getUniqueID());
|
||||
}
|
||||
nbttagcompound.setFloat("damageMultiplier", damageMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buf){
|
||||
if(buf.isReadable()){
|
||||
Block block = Block.REGISTRY.getObjectById(buf.readInt());
|
||||
try{
|
||||
fallTile.set(this, block.getStateFromMeta(buf.readInt()));
|
||||
}catch(IllegalAccessException e){
|
||||
Wizardry.logger.error("Error reading levitating block data from packet: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buf){
|
||||
if(getBlock() != null){
|
||||
buf.writeInt(Block.REGISTRY.getIDForObject(getBlock().getBlock()));
|
||||
buf.writeInt(getBlock().getBlock().getMetaFromState(getBlock()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package electroblob.wizardry.entity;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import electroblob.wizardry.spell.Meteor;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.MoverType;
|
||||
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.util.SoundCategory;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
@@ -20,6 +20,7 @@ public class EntityMeteor extends EntityFallingBlock {
|
||||
* The entity blast multiplier.
|
||||
*/
|
||||
public float blastMultiplier;
|
||||
private boolean damageBlocks;
|
||||
|
||||
public EntityMeteor(World world){
|
||||
super(world);
|
||||
@@ -27,11 +28,12 @@ public class EntityMeteor extends EntityFallingBlock {
|
||||
this.setSize(0.98F, 0.98F);
|
||||
}
|
||||
|
||||
public EntityMeteor(World world, double x, double y, double z, float blastMultiplier){
|
||||
public EntityMeteor(World world, double x, double y, double z, float blastMultiplier, boolean damageBlocks){
|
||||
super(world, x, y, z, WizardryBlocks.meteor.getDefaultState());
|
||||
this.motionY = -1.0D;
|
||||
this.setFire(200);
|
||||
this.blastMultiplier = blastMultiplier;
|
||||
this.damageBlocks = damageBlocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,7 +45,7 @@ public class EntityMeteor extends EntityFallingBlock {
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 16 == 1 && world.isRemote){
|
||||
Wizardry.proxy.playMovingSound(this, WizardrySounds.SPELL_LOOP_FIRE, 3.0f, 1.0f, false);
|
||||
Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_METEOR_FALLING, WizardrySounds.SPELLS, 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.
|
||||
@@ -66,21 +68,9 @@ public class EntityMeteor extends EntityFallingBlock {
|
||||
this.motionX *= 0.699999988079071D;
|
||||
this.motionZ *= 0.699999988079071D;
|
||||
this.motionY *= -0.5D;
|
||||
this.world.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.world,
|
||||
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.world.setBlockState(new BlockPos(this.posX + i1, y, this.posZ + j1),
|
||||
Blocks.FIRE.getDefaultState());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.world.newExplosion(this, this.posX, this.posY, this.posZ,
|
||||
Spells.meteor.getProperty(Meteor.BLAST_STRENGTH).floatValue() * blastMultiplier,
|
||||
damageBlocks, damageBlocks);
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
@@ -123,12 +113,19 @@ public class EntityMeteor extends EntityFallingBlock {
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
blastMultiplier = nbttagcompound.getFloat("blastMultiplier");
|
||||
damageBlocks = nbttagcompound.getBoolean("damageBlocks");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setFloat("blastMultiplier", blastMultiplier);
|
||||
nbttagcompound.setBoolean("damageBlocks", damageBlocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoundCategory getSoundCategory(){
|
||||
return WizardrySounds.SPELLS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package electroblob.wizardry.entity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.data.WizardData;
|
||||
import electroblob.wizardry.item.ISpellCastingItem;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Shield;
|
||||
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.SoundCategory;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class EntityShield extends Entity {
|
||||
|
||||
public WeakReference<EntityPlayer> player;
|
||||
@@ -46,8 +48,8 @@ public class EntityShield extends Entity {
|
||||
entityplayer.posY + 1 + entityplayer.getLookVec().y * 0.3,
|
||||
entityplayer.posZ + entityplayer.getLookVec().z * 0.3, entityplayer.rotationYawHead,
|
||||
entityplayer.rotationPitch);
|
||||
if(!entityplayer.isHandActive() || !(entityplayer.getHeldItem(entityplayer.getActiveHand()).getItem() instanceof ItemWand)){
|
||||
WizardData.get(entityplayer).shield = null;
|
||||
if(!entityplayer.isHandActive() || !(entityplayer.getHeldItem(entityplayer.getActiveHand()).getItem() instanceof ISpellCastingItem)){
|
||||
WizardData.get(entityplayer).setVariable(Shield.SHIELD_KEY, null);
|
||||
this.setDead();
|
||||
}
|
||||
}else if(!world.isRemote){
|
||||
@@ -62,13 +64,19 @@ public class EntityShield extends Entity {
|
||||
this.setRotation(par7, par8);
|
||||
}
|
||||
|
||||
public boolean attackEntityFrom(DamageSource par1DamageSource, float par2){
|
||||
if(par1DamageSource != null && par1DamageSource.getImmediateSource() instanceof IProjectile){
|
||||
par1DamageSource.getImmediateSource().playSound(WizardrySounds.SPELL_DEFLECTION, 0.3f, 1.3f);
|
||||
public boolean attackEntityFrom(DamageSource source, float damage){
|
||||
if(source != null && source.getImmediateSource() instanceof IProjectile){
|
||||
world.playSound(null, source.getImmediateSource().posX, source.getImmediateSource().posY,
|
||||
source.getImmediateSource().posZ, WizardrySounds.ENTITY_SHIELD_DEFLECT, WizardrySounds.SPELLS, 0.3f, 1.3f);
|
||||
}
|
||||
super.attackEntityFrom(par1DamageSource, par2);
|
||||
super.attackEntityFrom(source, damage);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoundCategory getSoundCategory(){
|
||||
return WizardrySounds.SPELLS;
|
||||
}
|
||||
|
||||
public boolean canBeCollidedWith(){
|
||||
return !this.isDead;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package electroblob.wizardry.entity;
|
||||
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
/** This interface allows implementing entity classes to define their own hitbox for wizardry's raytracing and
|
||||
* particle collision methods. Typically, entities implementing this interface will return null from the collision
|
||||
* bounding box methods in {@code Entity} (there are two for some reason) <b>but</b> return true from
|
||||
* {@link net.minecraft.entity.Entity#canBeCollidedWith()} */
|
||||
public interface ICustomHitbox {
|
||||
|
||||
/**
|
||||
* Calculates the point at which the line starting at the given origin and ending at the given endpoint hits this
|
||||
* entity, if any. Used in raytracing to allow entities to define fully custom behaviour. See
|
||||
* {@link electroblob.wizardry.entity.construct.EntityForcefield} for an example implementation of a spherical hitbox.
|
||||
* @param origin The origin of the line.
|
||||
* @param endpoint The endpoint of the line.
|
||||
* @param fuzziness Maximum distance around the line that should still count as a hit.
|
||||
* @return A {@link Vec3d} representing the point hit, or null if there is no intercept. This should be the first
|
||||
* point that the line hits, i.e. if there is more than one intercept this method should return the one nearest to
|
||||
* the given origin.
|
||||
*/
|
||||
Vec3d calculateIntercept(Vec3d origin, Vec3d endpoint, float fuzziness);
|
||||
|
||||
/**
|
||||
* Returns whether the given point is inside this entity.
|
||||
* @param point The coordinates to test.
|
||||
* @return True if the point is inside this entity, false if not.
|
||||
*/
|
||||
boolean contains(Vec3d point);
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package electroblob.wizardry.entity.construct;
|
||||
import net.minecraft.entity.projectile.EntityTippedArrow;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityArrowRain extends EntityMagicConstruct {
|
||||
@@ -20,9 +21,9 @@ public class EntityArrowRain extends EntityMagicConstruct {
|
||||
if(!this.world.isRemote){
|
||||
EntityTippedArrow arrow = new EntityTippedArrow(world, 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.motionX = MathHelper.cos((float)Math.toRadians(this.rotationYaw + 90));
|
||||
arrow.motionY = -0.6;
|
||||
arrow.motionZ = Math.sin(Math.toRadians(this.rotationYaw + 90));
|
||||
arrow.motionZ = MathHelper.sin((float)Math.toRadians(this.rotationYaw + 90));
|
||||
arrow.shootingEntity = this.getCaster();
|
||||
arrow.setDamage(7.0d * damageMultiplier);
|
||||
arrow.setPotionEffect(new ItemStack(Items.ARROW));
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
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.EntityPlayer;
|
||||
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;
|
||||
@@ -16,7 +18,11 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityBlackHole extends EntityMagicConstruct {
|
||||
|
||||
private static final double SUCTION_STRENGTH = 0.075;
|
||||
|
||||
public int[] randomiser;
|
||||
public int[] randomiser2;
|
||||
@@ -69,42 +75,49 @@ public class EntityBlackHole extends EntityMagicConstruct {
|
||||
}
|
||||
|
||||
if(this.lifetime - this.ticksExisted == 75){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_TRIGGER, 1.5f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_BLACK_HOLE_VANISH, 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);
|
||||
this.playSound(WizardrySounds.ENTITY_BLACK_HOLE_AMBIENT, 1.5f, 1.0f);
|
||||
}
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(6.0d, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
|
||||
if(!this.world.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(6.0d, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
|
||||
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 the target can't be moved, it isn't sucked in but is still damaged if it gets too close
|
||||
if(!(target instanceof EntityPlayer && ((getCaster() instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther)
|
||||
|| ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring)))){
|
||||
|
||||
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;
|
||||
}
|
||||
WizardryUtilities.undoGravity(target);
|
||||
|
||||
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;
|
||||
}
|
||||
// Sucks the target in
|
||||
if(this.posX > target.posX && target.motionX < 1){
|
||||
target.motionX += SUCTION_STRENGTH;
|
||||
}else if(this.posX < target.posX && target.motionX > -1){
|
||||
target.motionX -= SUCTION_STRENGTH;
|
||||
}
|
||||
|
||||
// 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.posY > target.posY && target.motionY < 1){
|
||||
target.motionY += SUCTION_STRENGTH;
|
||||
}else if(this.posY < target.posY && target.motionY > -1){
|
||||
target.motionY -= SUCTION_STRENGTH;
|
||||
}
|
||||
|
||||
if(this.posZ > target.posZ && target.motionZ < 1){
|
||||
target.motionZ += SUCTION_STRENGTH;
|
||||
}else if(this.posZ < target.posZ && target.motionZ > -1){
|
||||
target.motionZ -= SUCTION_STRENGTH;
|
||||
}
|
||||
|
||||
// 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.getDistance(target) <= 2){
|
||||
@@ -128,4 +141,9 @@ public class EntityBlackHole extends EntityMagicConstruct {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass){
|
||||
return pass == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -14,6 +14,8 @@ import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityBlizzard extends EntityMagicConstruct {
|
||||
|
||||
public EntityBlizzard(World world){
|
||||
@@ -25,14 +27,18 @@ public class EntityBlizzard extends EntityMagicConstruct {
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 120 == 1){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_BLIZZARD_AMBIENT, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// This is a good example of why you might define a spell base property without necessarily using it in the
|
||||
// spell - in fact, blizzard doesn't even have a spell class (yet)
|
||||
double radius = Spells.blizzard.getProperty(Spell.EFFECT_RADIUS).doubleValue();
|
||||
|
||||
if(!this.world.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(3.0d, this.posX, this.posY,
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
@@ -59,7 +65,7 @@ public class EntityBlizzard extends EntityMagicConstruct {
|
||||
for(int i=1; i<12; i++){
|
||||
double speed = (rand.nextBoolean() ? 1 : -1) * 0.1 + 0.05 * rand.nextDouble();
|
||||
ParticleBuilder.create(Type.SNOW).pos(this.posX, this.posY + rand.nextDouble() * 3, this.posZ).vel(0, 0, 0)
|
||||
.time(100).scale(2).spin(rand.nextDouble() * 2.5 + 0.5, speed).spawn(world);
|
||||
.time(100).scale(2).spin(rand.nextDouble() * (radius - 0.5) + 0.5, speed).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Entrapment;
|
||||
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.entity.MoverType;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
@@ -17,6 +17,8 @@ import net.minecraftforge.event.entity.living.LivingAttackEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
@Mod.EventBusSubscriber
|
||||
public class EntityBubble extends EntityMagicConstruct {
|
||||
|
||||
@@ -46,7 +48,7 @@ public class EntityBubble extends EntityMagicConstruct {
|
||||
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));
|
||||
this.rider = new WeakReference<>((EntityLivingBase)WizardryUtilities.getRider(this));
|
||||
}
|
||||
|
||||
// Prevents dismounting
|
||||
@@ -62,7 +64,8 @@ public class EntityBubble extends EntityMagicConstruct {
|
||||
|
||||
if(isDarkOrb){
|
||||
|
||||
if(WizardryUtilities.getRider(this) != null && this.ticksExisted % 30 == 0){
|
||||
if(WizardryUtilities.getRider(this) != null
|
||||
&& this.ticksExisted % Spells.entrapment.getProperty(Entrapment.DAMAGE_INTERVAL).intValue() == 0){
|
||||
if(this.getCaster() != null){
|
||||
WizardryUtilities.getRider(this).attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC),
|
||||
@@ -81,16 +84,16 @@ public class EntityBubble extends EntityMagicConstruct {
|
||||
(this.rand.nextDouble() - 0.5D) * 2.0D);
|
||||
}
|
||||
if(lifetime - this.ticksExisted == 75){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_TRIGGER, 1.5f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_ENTRAPMENT_VANISH, 1.5f, 1.0f);
|
||||
}else if(this.ticksExisted % 100 == 1 && this.ticksExisted < 150){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.5f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_ENTRAPMENT_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);
|
||||
if(!this.isDarkOrb) this.playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f);
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
@@ -100,7 +103,7 @@ public class EntityBubble extends EntityMagicConstruct {
|
||||
if(WizardryUtilities.getRider(this) != null){
|
||||
((EntityLivingBase)WizardryUtilities.getRider(this)).dismountEntity(this);
|
||||
}
|
||||
if(!this.isDarkOrb) this.playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f);
|
||||
if(!this.isDarkOrb) this.playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f);
|
||||
super.despawn();
|
||||
}
|
||||
|
||||
@@ -133,7 +136,7 @@ public class EntityBubble extends EntityMagicConstruct {
|
||||
// Bursts bubble when the creature inside takes damage
|
||||
if(event.getEntityLiving().getRidingEntity() instanceof EntityBubble
|
||||
&& !((EntityBubble)event.getEntityLiving().getRidingEntity()).isDarkOrb){
|
||||
event.getEntityLiving().getRidingEntity().playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f);
|
||||
event.getEntityLiving().getRidingEntity().playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f);
|
||||
event.getEntityLiving().getRidingEntity().setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityCombustionRune extends EntityMagicConstruct {
|
||||
|
||||
public EntityCombustionRune(World world){
|
||||
super(world);
|
||||
this.height = 0.2f;
|
||||
this.width = 2.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.world.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(width/2, posX, posY, posZ, world);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
float strength = Spells.combustion_rune.getProperty(Spell.BLAST_RADIUS).floatValue();
|
||||
|
||||
world.newExplosion(this.getCaster(), this.posX, this.posY, this.posZ, strength, true, true);
|
||||
|
||||
// The trap is destroyed once triggered.
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
}else if(this.rand.nextInt(15) == 0){
|
||||
double radius = 0.5 + rand.nextDouble() * 0.3;
|
||||
float angle = rand.nextFloat() * (float)Math.PI * 2;
|
||||
world.spawnParticle(EnumParticleTypes.FLAME, this.posX + radius * MathHelper.cos(angle), this.posY + 0.1,
|
||||
this.posZ + radius * MathHelper.sin(angle), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit(){}
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire(){
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityDecay extends EntityMagicConstruct {
|
||||
|
||||
public int textureIndex = 0;
|
||||
@@ -29,7 +32,7 @@ public class EntityDecay extends EntityMagicConstruct {
|
||||
super.onUpdate();
|
||||
|
||||
if(this.rand.nextInt(700) == 0 && this.ticksExisted + 100 < lifetime)
|
||||
this.playSound(SoundEvents.BLOCK_LAVA_AMBIENT, 0.2F + rand.nextFloat() * 0.2F,
|
||||
this.playSound(WizardrySounds.ENTITY_DECAY_AMBIENT, 0.2F + rand.nextFloat() * 0.2F,
|
||||
0.6F + rand.nextFloat() * 0.15F);
|
||||
|
||||
if(!this.world.isRemote){
|
||||
@@ -41,18 +44,19 @@ public class EntityDecay extends EntityMagicConstruct {
|
||||
// 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));
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.decay,
|
||||
Spells.decay.getProperty(Spell.EFFECT_DURATION).intValue(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
}else if(this.rand.nextInt(15) == 0){
|
||||
|
||||
double radius = rand.nextDouble() * 0.8;
|
||||
double angle = rand.nextDouble() * Math.PI * 2;
|
||||
float angle = rand.nextFloat() * (float)Math.PI * 2;
|
||||
float brightness = rand.nextFloat() * 0.4f;
|
||||
|
||||
ParticleBuilder.create(Type.DARK_MAGIC)
|
||||
.pos(this.posX + radius * Math.cos(angle), this.posY, this.posZ + radius * Math.sin(angle))
|
||||
.pos(this.posX + radius * MathHelper.cos(angle), this.posY, this.posZ + radius * MathHelper.sin(angle))
|
||||
.clr(brightness, 0, brightness + 0.1f)
|
||||
.spawn(world);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Earthquake;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
@@ -13,8 +13,11 @@ 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.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityEarthquake extends EntityMagicConstruct {
|
||||
|
||||
public EntityEarthquake(World world){
|
||||
@@ -27,21 +30,20 @@ public class EntityEarthquake extends EntityMagicConstruct {
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
double speed = Spells.earthquake.getProperty(Earthquake.SPREAD_SPEED).doubleValue();
|
||||
|
||||
if(!world.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)){
|
||||
for(float 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));
|
||||
// leave blocks in the centre untouched.
|
||||
int x = this.posX < 0 ? (int)(this.posX + ((this.ticksExisted * speed) + 1.5) * MathHelper.sin(angle) - 1)
|
||||
: (int)(this.posX + ((this.ticksExisted * speed) + 1.5) * MathHelper.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));
|
||||
int z = this.posZ < 0 ? (int)(this.posZ + ((this.ticksExisted * speed) + 1.5) * MathHelper.cos(angle) - 1)
|
||||
: (int)(this.posZ + ((this.ticksExisted * speed) + 1.5) * MathHelper.cos(angle));
|
||||
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
|
||||
@@ -58,50 +60,53 @@ public class EntityEarthquake extends EntityMagicConstruct {
|
||||
}
|
||||
}
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities
|
||||
.getEntitiesWithinRadius((this.ticksExisted * speed) + 1.5, this.posX, this.posY, this.posZ, world);
|
||||
}
|
||||
|
||||
// In this particular instance, the caster is completely unaffected because they will always be in the
|
||||
// centre.
|
||||
targets.remove(this.getCaster());
|
||||
List<EntityLivingBase> targets = WizardryUtilities
|
||||
.getEntitiesWithinRadius((this.ticksExisted * speed) + 1.5, this.posX, this.posY, this.posZ, world);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
// In this particular instance, the caster is completely unaffected because they will always be in the
|
||||
// centre.
|
||||
targets.remove(this.getCaster());
|
||||
|
||||
// Searches in a 1 wide ring.
|
||||
if(this.getDistance(target) > (this.ticksExisted * speed) + 0.5 && target.posY < this.posY + 1
|
||||
&& target.posY > this.posY - 1){
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
// Knockback must be removed in this instance, or the target will fall into the floor.
|
||||
double motionX = target.motionX;
|
||||
double motionZ = target.motionZ;
|
||||
// Searches in a 1 wide ring.
|
||||
if(this.getDistance(target) > (this.ticksExisted * speed) + 0.5 && target.posY < this.posY + 1
|
||||
&& target.posY > this.posY - 1){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.BLAST),
|
||||
10 * this.damageMultiplier);
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 400, 1));
|
||||
}
|
||||
// Knockback must be removed in this instance, or the target will fall into the floor.
|
||||
double motionX = target.motionX;
|
||||
double motionZ = target.motionZ;
|
||||
|
||||
// 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;
|
||||
if(this.isValidTarget(target)){
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.BLAST),
|
||||
10 * this.damageMultiplier);
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 400, 1));
|
||||
}
|
||||
|
||||
// Player motion is handled on that player's client so needs packets
|
||||
if(target instanceof EntityPlayerMP){
|
||||
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// Constant 15 blocks for now
|
||||
List<EntityPlayer> targets = WizardryUtilities.getEntitiesWithinRadius(15, posX, posY, posZ, world, EntityPlayer.class);
|
||||
}
|
||||
|
||||
float magnitude = 6f * ((float)(this.lifetime - this.ticksExisted))/(float)this.lifetime;
|
||||
if(!world.isRemote){
|
||||
// Constant 15 blocks for now
|
||||
List<EntityPlayer> targets2 = WizardryUtilities.getEntitiesWithinRadius(15, posX, posY, posZ, world, EntityPlayer.class);
|
||||
|
||||
float magnitude = 10f * ((float)(this.lifetime - this.ticksExisted))/(float)this.lifetime;
|
||||
|
||||
// Makes the screen shake
|
||||
for(EntityPlayer target : targets){
|
||||
target.rotationPitch = this.ticksExisted % 2 == 0 ? magnitude : -magnitude;
|
||||
for(EntityPlayer target : targets2){
|
||||
target.rotationPitch += this.ticksExisted % 2 == 0 ? magnitude : -magnitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityFireRing extends EntityMagicConstruct {
|
||||
|
||||
// TODO: Implement blast modifiers
|
||||
|
||||
public EntityFireRing(World world){
|
||||
super(world);
|
||||
this.height = 1.0f;
|
||||
@@ -21,7 +25,7 @@ public class EntityFireRing extends EntityMagicConstruct {
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 40 == 1){
|
||||
this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 4.0f, 0.7f);
|
||||
this.playSound(WizardrySounds.ENTITY_FIRE_RING_AMBIENT, 4.0f, 0.7f);
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
@@ -41,14 +45,15 @@ public class EntityFireRing extends EntityMagicConstruct {
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)){
|
||||
|
||||
target.setFire(10);
|
||||
target.setFire(Spells.ring_of_fire.getProperty(Spell.BURN_DURATION).intValue());
|
||||
|
||||
float damage = Spells.ring_of_fire.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
if(this.getCaster() != null){
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.FIRE),
|
||||
1 * damageMultiplier);
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(),
|
||||
DamageType.FIRE), damage);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier);
|
||||
target.attackEntityFrom(DamageSource.MAGIC, damage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
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.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityFireSigil extends EntityMagicConstruct {
|
||||
|
||||
public EntityFireSigil(World world){
|
||||
@@ -38,16 +41,18 @@ public class EntityFireSigil extends EntityMagicConstruct {
|
||||
|
||||
target.attackEntityFrom(this.getCaster() != null
|
||||
? MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.FIRE)
|
||||
: DamageSource.MAGIC, 6);
|
||||
: DamageSource.MAGIC, Spells.fire_sigil.getProperty(Spell.DAMAGE).floatValue()
|
||||
* damageMultiplier);
|
||||
|
||||
// Removes knockback
|
||||
target.motionX = velX;
|
||||
target.motionY = velY;
|
||||
target.motionZ = velZ;
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) target.setFire(10);
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, target))
|
||||
target.setFire(Spells.fire_sigil.getProperty(Spell.BURN_DURATION).intValue());
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
|
||||
this.playSound(WizardrySounds.ENTITY_FIRE_SIGIL_TRIGGER, 1, 1);
|
||||
|
||||
// The trap is destroyed once triggered.
|
||||
this.setDead();
|
||||
@@ -55,9 +60,9 @@ public class EntityFireSigil extends EntityMagicConstruct {
|
||||
}
|
||||
}else if(this.rand.nextInt(15) == 0){
|
||||
double radius = 0.5 + rand.nextDouble() * 0.3;
|
||||
double angle = rand.nextDouble() * Math.PI * 2;
|
||||
world.spawnParticle(EnumParticleTypes.FLAME, this.posX + radius * Math.cos(angle), this.posY + 0.1,
|
||||
this.posZ + radius * Math.sin(angle), 0, 0, 0);
|
||||
float angle = rand.nextFloat() * (float)Math.PI * 2;;
|
||||
world.spawnParticle(EnumParticleTypes.FLAME, this.posX + radius * MathHelper.cos(angle), this.posY + 0.1,
|
||||
this.posZ + radius * MathHelper.sin(angle), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,89 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.entity.ICustomHitbox;
|
||||
import electroblob.wizardry.entity.projectile.EntityMagicArrow;
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.item.EntityXPOrb;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.entity.projectile.EntityArrow;
|
||||
import net.minecraft.entity.projectile.EntityThrowable;
|
||||
import net.minecraft.network.play.server.SPacketEntityVelocity;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.event.entity.living.LivingAttackEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.event.world.ExplosionEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
public class EntityForcefield extends EntityMagicConstruct {
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@Mod.EventBusSubscriber
|
||||
public class EntityForcefield extends EntityMagicConstruct implements ICustomHitbox {
|
||||
|
||||
/** Extra radius to search around the forcefield for incoming entities. Any entities with a velocity greater than
|
||||
* this could potentially penetrate the forcefield. */
|
||||
private static final double SEARCH_BORDER_SIZE = 4;
|
||||
|
||||
private static final float BOUNCINESS = 0.2f;
|
||||
|
||||
private float radius;
|
||||
|
||||
public EntityForcefield(World world){
|
||||
super(world);
|
||||
this.height = 6;
|
||||
this.width = 6;
|
||||
setRadius(3); // Shouldn't be needed but it's a good failsafe
|
||||
this.ignoreFrustumCheck = true;
|
||||
this.noClip = true;
|
||||
}
|
||||
|
||||
public void setRadius(float radius){
|
||||
this.radius = radius;
|
||||
this.height = 2 * radius;
|
||||
this.width = 2 * radius;
|
||||
// y-3 because it needs to be centred on the given position
|
||||
this.setEntityBoundingBox(new AxisAlignedBB(posX - 3, posY - 3, posZ - 3, posX + 3, posY + 3, posZ + 3));
|
||||
this.setEntityBoundingBox(new AxisAlignedBB(posX - radius, posY - radius, posZ - radius,
|
||||
posX + radius, posY + radius, posZ + radius));
|
||||
}
|
||||
|
||||
public float getRadius(){
|
||||
return radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeCollidedWith(){
|
||||
return !this.isDead;
|
||||
return false;//!this.isDead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getCollisionBox(Entity entity){
|
||||
return entity.getEntityBoundingBox();
|
||||
return null;//entity.getEntityBoundingBox();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AxisAlignedBB getCollisionBoundingBox(){
|
||||
return super.getCollisionBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass){
|
||||
return pass == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,48 +91,182 @@ public class EntityForcefield extends EntityMagicConstruct {
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.world.isRemote){
|
||||
// TESTME: This used to say posY+3, but I'm pretty sure that's wrong because of how the bounding box was set...
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(3.5, posX, posY, posZ, world);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
if(this.isValidTarget(target)){
|
||||
double multiplier = (3.5 - target.getDistance(this.posX, this.posY, this.posZ)) * 0.1;
|
||||
target.addVelocity((target.posX - this.posX) * multiplier,
|
||||
(target.posY - this.posY) * 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));
|
||||
// New forcefield repulsion system:
|
||||
// Searches for all entities near the forcefield and determines where they will be next tick.
|
||||
// If they will be inside the forcefield next tick, sets their position and velocity such that they appear to
|
||||
// bounce off the forcefield and creates impact particle effects and sounds where they hit it
|
||||
|
||||
List<Entity> targets = WizardryUtilities.getEntitiesWithinRadius(radius + SEARCH_BORDER_SIZE, posX, posY, posZ, world, Entity.class);
|
||||
|
||||
targets.remove(this);
|
||||
targets.removeIf(t -> t instanceof EntityXPOrb); // Gets annoying since they're attracted to the player
|
||||
|
||||
// Ring of the defender allows players to shoot through their own forcefields
|
||||
if(getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(),
|
||||
WizardryItems.ring_defender)){
|
||||
targets.removeIf(t -> t instanceof EntityMagicArrow && !this.isValidTarget(((EntityMagicArrow)t).getCaster())
|
||||
|| t instanceof EntityThrowable && !this.isValidTarget(((EntityThrowable)t).getThrower())
|
||||
|| t instanceof EntityArrow && !this.isValidTarget(((EntityArrow)t).shootingEntity));
|
||||
}
|
||||
|
||||
for(Entity target : targets){
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
|
||||
Vec3d currentPos = Arrays.stream(WizardryUtilities.getVertices(target.getEntityBoundingBox()))
|
||||
.min(Comparator.comparingDouble(v -> v.distanceTo(this.getPositionVector())))
|
||||
.orElse(target.getPositionVector()); // This will never happen, it's just here to make the compiler happy
|
||||
|
||||
double currentDistance = target.getDistance(this);
|
||||
|
||||
// Estimate the target's position next tick
|
||||
// We have to assume the same vertex is closest or the velocity will be wrong
|
||||
Vec3d nextTickPos = currentPos.add(target.motionX, target.motionY, target.motionZ);
|
||||
double nextTickDistance = nextTickPos.distanceTo(this.getPositionVector());
|
||||
|
||||
boolean flag;
|
||||
|
||||
if(WizardryUtilities.isLiving(target)){
|
||||
// Non-allied living entities shouldn't be inside at all
|
||||
flag = nextTickDistance <= radius;
|
||||
}else{
|
||||
// Non-living entities will bounce off if they hit the forcefield within the next tick...
|
||||
flag = (currentDistance > radius && nextTickDistance <= radius) // ...from the outside...
|
||||
|| (currentDistance < radius && nextTickDistance >= radius); // ...or from the inside
|
||||
}
|
||||
|
||||
if(flag){
|
||||
|
||||
// Ring of interdiction
|
||||
if(getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(),
|
||||
WizardryItems.ring_interdiction) && WizardryUtilities.isLiving(target)){
|
||||
target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(),
|
||||
MagicDamage.DamageType.MAGIC), 1);
|
||||
}
|
||||
|
||||
Vec3d targetRelativePos = currentPos.subtract(this.getPositionVector());
|
||||
|
||||
double nudgeVelocity = this.contains(target) ? -0.1 : 0.1;
|
||||
if(WizardryUtilities.isLiving(target)) nudgeVelocity = 0.25;
|
||||
Vec3d extraVelocity = targetRelativePos.normalize().scale(nudgeVelocity);
|
||||
|
||||
// ...make it bounce off!
|
||||
target.motionX = target.motionX * -BOUNCINESS + extraVelocity.x;
|
||||
target.motionY = target.motionY * -BOUNCINESS + extraVelocity.y;
|
||||
target.motionZ = target.motionZ * -BOUNCINESS + extraVelocity.z;
|
||||
|
||||
// Prevents the forcefield bouncing things into the floor
|
||||
if(target.onGround && target.motionY < 0) target.motionY = 0.1;
|
||||
|
||||
// How far the target needs to move towards the centre (negative means away from the centre)
|
||||
double distanceTowardsCentre = -(targetRelativePos.length() - radius) - (radius - nextTickDistance);
|
||||
Vec3d targetNewPos = target.getPositionVector().add(targetRelativePos.normalize().scale(distanceTowardsCentre));
|
||||
target.setPosition(targetNewPos.x, targetNewPos.y, targetNewPos.z);
|
||||
|
||||
world.playSound(target.posX, target.posY, target.posZ, WizardrySounds.ENTITY_FORCEFIELD_DEFLECT,
|
||||
WizardrySounds.SPELLS, 0.3f, 1.3f, false);
|
||||
|
||||
if(!world.isRemote){
|
||||
// Player motion is handled on that player's client so needs packets
|
||||
if(target instanceof EntityPlayerMP){
|
||||
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
Vec3d relativeImpactPos = targetRelativePos.normalize().scale(radius);
|
||||
|
||||
float yaw = (float)Math.atan2(relativeImpactPos.x, -relativeImpactPos.z);
|
||||
float pitch = (float)Math.asin(relativeImpactPos.y/ radius);
|
||||
|
||||
ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector().add(relativeImpactPos))
|
||||
.time(6).face((float)(yaw * 180/Math.PI), (float)(pitch * 180/Math.PI))
|
||||
.clr(0.9f, 0.95f, 1).spawn(world);
|
||||
|
||||
for(int i = 0; i < 12; i++){
|
||||
|
||||
float yaw1 = yaw + 0.3f * (rand.nextFloat() - 0.5f) - (float)Math.PI/2;
|
||||
float pitch1 = pitch + 0.3f * (rand.nextFloat() - 0.5f);
|
||||
|
||||
float brightness = rand.nextFloat();
|
||||
|
||||
double r = radius + 0.05;
|
||||
double x = this.posX + r * MathHelper.cos(yaw1) * MathHelper.cos(pitch1);
|
||||
double y = this.posY + r * MathHelper.sin(pitch1);
|
||||
double z = this.posZ + r * MathHelper.sin(yaw1) * MathHelper.cos(pitch1);
|
||||
|
||||
ParticleBuilder.create(Type.DUST).pos(x, y, z).time(6 + rand.nextInt(6))
|
||||
.face((float)(yaw1 * 180/Math.PI) + 90, (float)(pitch1 * 180/Math.PI)).scale(1.5f)
|
||||
.clr(0.7f + 0.3f * brightness, 0.85f + 0.15f * brightness, 1).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(int i = 1; i < 40; i++){
|
||||
|
||||
// Generates a spherical pattern of particles
|
||||
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;
|
||||
|
||||
ParticleBuilder.create(Type.DUST)
|
||||
.pos(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))
|
||||
.time(48 + this.rand.nextInt(12))
|
||||
.clr(brightness, brightness, 1.0f).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float damage){
|
||||
public boolean contains(Vec3d vec){
|
||||
return vec.distanceTo(this.getPositionVector()) < radius; // The surface counts as outside
|
||||
}
|
||||
|
||||
if(source != null && source.getImmediateSource() != null){
|
||||
// Now works for any source of damage.
|
||||
source.getImmediateSource().playSound(WizardrySounds.SPELL_DEFLECTION, 0.3f, 1.3f);
|
||||
/** Returns true if the given bounding box is completely inside this forcefield (the surface counts as outside). */
|
||||
public boolean contains(AxisAlignedBB box){
|
||||
return Arrays.stream(WizardryUtilities.getVertices(box)).allMatch(this::contains);
|
||||
}
|
||||
|
||||
/** Returns true if the given entity is completely inside this forcefield (the surface counts as outside). */
|
||||
public boolean contains(Entity entity){
|
||||
return contains(entity.getEntityBoundingBox());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3d calculateIntercept(Vec3d origin, Vec3d endpoint, float fuzziness){
|
||||
|
||||
// We want the intercept between the line and a sphere
|
||||
// First we need to find the point where the line is closest to the centre
|
||||
// Then we can use a bit of geometry to find the intercept
|
||||
|
||||
// Find the closest point to the centre
|
||||
// http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
|
||||
Vec3d line = endpoint.subtract(origin);
|
||||
double t = -origin.subtract(this.getPositionVector()).dotProduct(line) / line.lengthSquared();
|
||||
Vec3d closestPoint = origin.add(line.scale(t));
|
||||
// Now calculate the distance from that point to the centre (squared because that's all we need)
|
||||
double dsquared = closestPoint.squareDistanceTo(this.getPositionVector());
|
||||
double rsquared = Math.pow(radius + fuzziness, 2);
|
||||
// If the minimum distance is outside the radius (plus fuzziness) then there is no intercept
|
||||
if(dsquared > rsquared) return null;
|
||||
// Now do pythagoras to find the other side of the triangle, which is the distance along the line from
|
||||
// the closest point to the edge of the sphere, and go that far back towards the origin - and that's it!
|
||||
return closestPoint.subtract(line.normalize().scale(MathHelper.sqrt(rsquared - dsquared)));
|
||||
}
|
||||
|
||||
// Need to sync the caster because we're now dealing with client-side motion
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
super.writeSpawnData(data);
|
||||
data.writeFloat(getRadius());
|
||||
if(getCaster() != null) data.writeInt(getCaster().getEntityId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
|
||||
super.readSpawnData(data);
|
||||
|
||||
setRadius(data.readFloat());
|
||||
|
||||
if(!data.isReadable()) return;
|
||||
|
||||
Entity entity = world.getEntityByID(data.readInt());
|
||||
|
||||
if(entity instanceof EntityLivingBase){
|
||||
setCaster((EntityLivingBase)entity);
|
||||
}else{
|
||||
Wizardry.logger.warn("Forcefield caster with ID in spawn data not found");
|
||||
}
|
||||
super.attackEntityFrom(source, damage);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,4 +274,91 @@ public class EntityForcefield extends EntityMagicConstruct {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevents any kind of interactions or attacks through the forcefield
|
||||
// We may as well include projectile damage for this, then it will act as a failsafe
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLivingAttackEvent(LivingAttackEvent event){
|
||||
|
||||
if(event.getSource().getTrueSource() instanceof EntityPlayer && event.getSource().isProjectile()
|
||||
&& ItemArtefact.isArtefactActive((EntityPlayer)event.getSource().getTrueSource(), WizardryItems.ring_defender)){
|
||||
return; // Players wearing a ring of the defender can shoot stuff as normal, so don't cancel the event
|
||||
}
|
||||
|
||||
if(!event.getSource().isUnblockable() && event.getSource().getTrueSource() != null && event.getEntityLiving() != null
|
||||
&& !(event.getSource().getImmediateSource() instanceof EntityForcefield)){ // If the damage was from a forcefield that's ok
|
||||
// This condition will be false if both entities are outside a forcefield or both are in the same one
|
||||
if(getSurroundingForcefield(event.getEntityLiving()) != getSurroundingForcefield(event.getSource().getTrueSource())){
|
||||
event.setCanceled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static EntityForcefield getSurroundingForcefield(World world, Vec3d vec){
|
||||
|
||||
double searchRadius = 20;
|
||||
|
||||
List<EntityForcefield> forcefields = WizardryUtilities.getEntitiesWithinRadius(searchRadius, vec.x,
|
||||
vec.y, vec.z, world, EntityForcefield.class);
|
||||
|
||||
forcefields.removeIf(f -> !f.contains(vec));
|
||||
// There should only be one left at this point since we now have anti-overlap, but commands might bypass that
|
||||
return forcefields.stream().min(Comparator.comparingDouble(f -> vec.squareDistanceTo(f.getPositionVector())))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static EntityForcefield getSurroundingForcefield(World world, AxisAlignedBB box, Vec3d vec){
|
||||
|
||||
double searchRadius = 20;
|
||||
|
||||
List<EntityForcefield> forcefields = WizardryUtilities.getEntitiesWithinRadius(searchRadius, vec.x,
|
||||
vec.y, vec.z, world, EntityForcefield.class);
|
||||
|
||||
forcefields.removeIf(f -> !f.contains(box));
|
||||
// There should only be one left at this point since we now have anti-overlap, but commands might bypass that
|
||||
return forcefields.stream().min(Comparator.comparingDouble(f -> vec.squareDistanceTo(f.getPositionVector())))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static EntityForcefield getSurroundingForcefield(Entity entity){
|
||||
return getSurroundingForcefield(entity.world, entity.getEntityBoundingBox(), entity.getPositionVector());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerInteractEvent(PlayerInteractEvent event){
|
||||
|
||||
if(!event.isCancelable()) return; // We don't care about clicking empty space
|
||||
|
||||
// For some reason block bounding boxes are relative whereas entity bounding boxes are absolute
|
||||
AxisAlignedBB box = event.getWorld().getBlockState(event.getPos()).getBoundingBox(event.getWorld(), event.getPos())
|
||||
.offset(event.getPos().getX(), event.getPos().getY(), event.getPos().getZ());
|
||||
|
||||
if(event instanceof PlayerInteractEvent.EntityInteract){
|
||||
box = ((PlayerInteractEvent.EntityInteract)event).getTarget().getEntityBoundingBox();
|
||||
}else if(event instanceof PlayerInteractEvent.EntityInteractSpecific){
|
||||
box = ((PlayerInteractEvent.EntityInteractSpecific)event).getTarget().getEntityBoundingBox();
|
||||
}
|
||||
|
||||
// If the player is trying to interact across a forcefield boundary, cancel the event
|
||||
// The most pragmatic solution here is to use the centres - it's not perfect, but it's simple!
|
||||
if(getSurroundingForcefield(event.getWorld(), WizardryUtilities.getCentre(box))
|
||||
!= getSurroundingForcefield(event.getWorld(), event.getEntityPlayer().getPositionVector())){
|
||||
event.setCanceled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onExplosionEvent(ExplosionEvent event){
|
||||
|
||||
EntityForcefield forcefield = getSurroundingForcefield(event.getWorld(), event.getExplosion().getPosition());
|
||||
// Not a particularly efficient way of doing it but explosions are laggy anyway, and the code is neat :P
|
||||
event.getExplosion().getAffectedBlockPositions().removeIf(p -> getSurroundingForcefield(event.getWorld(),
|
||||
new Vec3d(p).add(0.5, 0.5, 0.5)) != forcefield);
|
||||
|
||||
event.getExplosion().getPlayerKnockbackMap().keySet().removeIf(p -> getSurroundingForcefield(p) != forcefield);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -12,8 +12,11 @@ import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityFrostSigil extends EntityMagicConstruct {
|
||||
|
||||
public EntityFrostSigil(World world){
|
||||
@@ -38,12 +41,15 @@ public class EntityFrostSigil extends EntityMagicConstruct {
|
||||
|
||||
WizardryUtilities.attackEntityWithoutKnockback(target, this.getCaster() != null
|
||||
? MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.FROST)
|
||||
: DamageSource.MAGIC, 8);
|
||||
: DamageSource.MAGIC, Spells.frost_sigil.getProperty(Spell.DAMAGE).floatValue()
|
||||
* damageMultiplier);
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, target))
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 1));
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost,
|
||||
Spells.frost_sigil.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.frost_sigil.getProperty(Spell.EFFECT_STRENGTH).intValue()));
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_FROST_SIGIL_TRIGGER, 1.0f, 1.0f);
|
||||
|
||||
// The trap is destroyed once triggered.
|
||||
this.setDead();
|
||||
@@ -51,9 +57,9 @@ public class EntityFrostSigil extends EntityMagicConstruct {
|
||||
}
|
||||
}else if(this.rand.nextInt(15) == 0){
|
||||
double radius = 0.5 + rand.nextDouble() * 0.3;
|
||||
double angle = rand.nextDouble() * Math.PI * 2;
|
||||
float angle = rand.nextFloat() * (float)Math.PI * 2;;
|
||||
ParticleBuilder.create(Type.SNOW)
|
||||
.pos(this.posX + radius * Math.cos(angle), this.posY + 0.1, this.posZ + radius * Math.sin(angle))
|
||||
.pos(this.posX + radius * MathHelper.cos(angle), this.posY + 0.1, this.posZ + radius * MathHelper.sin(angle))
|
||||
.vel(0, 0, 0) // Required since default for snow is not stationary
|
||||
.spawn(world);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import electroblob.wizardry.entity.projectile.EntityIceShard;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityHailstorm extends EntityMagicConstruct {
|
||||
@@ -20,9 +21,9 @@ public class EntityHailstorm extends EntityMagicConstruct {
|
||||
EntityIceShard iceshard = new EntityIceShard(world);
|
||||
iceshard.setPosition(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.motionX = MathHelper.cos((float)Math.toRadians(this.rotationYaw + 90));
|
||||
iceshard.motionY = -0.6;
|
||||
iceshard.motionZ = Math.sin(Math.toRadians(this.rotationYaw + 90));
|
||||
iceshard.motionZ = MathHelper.sin((float)Math.toRadians(this.rotationYaw + 90));
|
||||
iceshard.setCaster(this.getCaster());
|
||||
iceshard.damageMultiplier = this.damageMultiplier;
|
||||
this.world.spawnEntity(iceshard);
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.item.ItemLightningHammer;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.LightningHammer;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.MoverType;
|
||||
import net.minecraft.entity.effect.EntityLightningBolt;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityHammer extends EntityMagicConstruct {
|
||||
|
||||
/** How long the hammer has been falling for. */
|
||||
public int fallTime;
|
||||
|
||||
public boolean spin = false;
|
||||
|
||||
public EntityHammer(World world){
|
||||
super(world);
|
||||
this.setSize(1.0f, 1.9F);
|
||||
@@ -51,15 +63,20 @@ public class EntityHammer extends EntityMagicConstruct {
|
||||
return this.getEntityBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyEntityCollision(Entity entity){
|
||||
super.applyEntityCollision(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(this.ticksExisted % 20 == 1 && !this.onGround && world.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.ticksExisted % 20 == 1 && !this.onGround && world.isRemote){
|
||||
// // Though this sound does repeat, it stops when it hits the ground.
|
||||
// Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_HAMMER_FALLING, WizardrySounds.SPELLS, 3.0f, 1.0f, false);
|
||||
// }
|
||||
|
||||
if(this.world.isRemote && this.ticksExisted % 3 == 0){
|
||||
ParticleBuilder.create(Type.SPARK)
|
||||
@@ -67,65 +84,81 @@ public class EntityHammer extends EntityMagicConstruct {
|
||||
.spawn(world);
|
||||
}
|
||||
|
||||
if(!this.world.isRemote){
|
||||
this.prevPosX = this.posX;
|
||||
this.prevPosY = this.posY;
|
||||
this.prevPosZ = this.posZ;
|
||||
++this.fallTime;
|
||||
this.motionY -= 0.03999999910593033D;
|
||||
this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ);
|
||||
this.motionX *= 0.9800000190734863D;
|
||||
this.motionY *= 0.9800000190734863D;
|
||||
this.motionZ *= 0.9800000190734863D;
|
||||
|
||||
this.prevPosX = this.posX;
|
||||
this.prevPosY = this.posY;
|
||||
this.prevPosZ = this.posZ;
|
||||
++this.fallTime;
|
||||
this.motionY -= 0.03999999910593033D;
|
||||
this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ);
|
||||
this.motionX *= 0.9800000190734863D;
|
||||
this.motionY *= 0.9800000190734863D;
|
||||
this.motionZ *= 0.9800000190734863D;
|
||||
if(this.onGround){
|
||||
|
||||
if(this.onGround){
|
||||
this.motionX *= 0.699999988079071D;
|
||||
this.motionZ *= 0.699999988079071D;
|
||||
this.motionY *= -0.5D;
|
||||
|
||||
this.motionX *= 0.699999988079071D;
|
||||
this.motionZ *= 0.699999988079071D;
|
||||
this.motionY *= -0.5D;
|
||||
this.rotationPitch = 0;
|
||||
this.spin = false;
|
||||
|
||||
if(this.ticksExisted % 40 == 0){
|
||||
if(this.ticksExisted % Spells.lightning_hammer.getProperty(LightningHammer.ATTACK_INTERVAL).floatValue() == 0){
|
||||
|
||||
double seekerRange = 10.0d;
|
||||
double seekerRange = Spells.lightning_hammer.getProperty(Spell.EFFECT_RADIUS).doubleValue();
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX,
|
||||
this.posY + 1, this.posZ, world);
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX,
|
||||
this.posY + 1, this.posZ, world);
|
||||
|
||||
// For this spell there is no limit to the amount of secondary targets!
|
||||
for(EntityLivingBase target : targets){
|
||||
int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue();
|
||||
while(targets.size() > maxTargets) targets.remove(targets.size() - 1);
|
||||
|
||||
if(this.isValidTarget(target)){
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(world.isRemote){
|
||||
|
||||
ParticleBuilder.create(Type.LIGHTNING).pos(posX, posY + height - 0.1, posZ) .target(target).spawn(world);
|
||||
|
||||
ParticleBuilder.spawnShockParticles(world, target.posX,
|
||||
target.getEntityBoundingBox().minY + target.height, target.posZ);
|
||||
}
|
||||
if(WizardryUtilities.isLiving(target) && this.isValidTarget(target)){
|
||||
|
||||
target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, rand.nextFloat() * 0.4F + 1.5F);
|
||||
if(world.isRemote){
|
||||
|
||||
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);
|
||||
}
|
||||
ParticleBuilder.create(Type.LIGHTNING).pos(posX, posY + height - 0.1, posZ) .target(target).spawn(world);
|
||||
|
||||
ParticleBuilder.spawnShockParticles(world, target.posX,
|
||||
target.getEntityBoundingBox().minY + target.height, target.posZ);
|
||||
}
|
||||
|
||||
target.playSound(WizardrySounds.ENTITY_HAMMER_ATTACK, 1.0F, rand.nextFloat() * 0.4F + 1.5F);
|
||||
|
||||
float damage = Spells.lightning_hammer.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
if(this.getCaster() != null){
|
||||
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(
|
||||
this, getCaster(), DamageType.SHOCK), damage);
|
||||
WizardryUtilities.applyStandardKnockback(this, target);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.MAGIC, damage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
if(spin) this.setRotation(this.rotationYaw, this.rotationPitch + 15);
|
||||
|
||||
List<Entity> collided = world.getEntitiesInAABBexcluding(this, this.getCollisionBoundingBox(), e -> e instanceof EntityLivingBase);
|
||||
|
||||
float damage = Spells.lightning_hammer.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
for(Entity entity : collided){
|
||||
entity.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), damage);
|
||||
//if(entity instanceof EntityLivingBase) ((EntityLivingBase)entity).knockBack(this, 2, -this.motionX, -this.motionZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void despawn(){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_EXPLODE, 1.0F, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_HAMMER_EXPLODE, 1.0F, 1.0f);
|
||||
|
||||
if(this.world.isRemote){
|
||||
this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
@@ -156,6 +189,30 @@ public class EntityHammer extends EntityMagicConstruct {
|
||||
false);
|
||||
world.addWeatherEffect(entitylightning);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.ENTITY_HAMMER_LAND, 1.0F, 0.6f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processInitialInteract(EntityPlayer player, EnumHand hand){
|
||||
|
||||
if(player == this.getCaster() && ItemArtefact.isArtefactActive(player, WizardryItems.ring_hammer)
|
||||
&& player.getHeldItemMainhand().isEmpty() && ticksExisted > 10){
|
||||
|
||||
this.setDead();
|
||||
|
||||
ItemStack hammer = new ItemStack(WizardryItems.lightning_hammer);
|
||||
if(!hammer.hasTagCompound()) hammer.setTagCompound(new NBTTagCompound());
|
||||
hammer.getTagCompound().setInteger(ItemLightningHammer.DURATION_NBT_KEY, lifetime);
|
||||
hammer.setItemDamage(ticksExisted);
|
||||
hammer.getTagCompound().setFloat(ItemLightningHammer.DAMAGE_MULTIPLIER_NBT_KEY, damageMultiplier);
|
||||
|
||||
player.setHeldItem(EnumHand.MAIN_HAND, hammer);
|
||||
return true;
|
||||
|
||||
}else{
|
||||
return super.processInitialInteract(player, hand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,12 +220,39 @@ public class EntityHammer extends EntityMagicConstruct {
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setByte("Time", (byte)this.fallTime);
|
||||
nbttagcompound.setBoolean("Spin", spin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.fallTime = nbttagcompound.getByte("Time") & 255;
|
||||
this.spin = nbttagcompound.getBoolean("Spin");
|
||||
}
|
||||
|
||||
// Need to sync the caster so they don't have particles spawned at them
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
super.writeSpawnData(data);
|
||||
data.writeBoolean(spin);
|
||||
if(getCaster() != null) data.writeInt(getCaster().getEntityId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
super.readSpawnData(data);
|
||||
spin = data.readBoolean();
|
||||
|
||||
if(!data.isReadable()) return;
|
||||
|
||||
Entity entity = world.getEntityByID(data.readInt());
|
||||
|
||||
if(entity instanceof EntityLivingBase){
|
||||
setCaster((EntityLivingBase)entity);
|
||||
}else{
|
||||
Wizardry.logger.warn("Lightning hammer caster with ID in spawn data not found");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -10,10 +10,15 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityHealAura extends EntityMagicConstruct {
|
||||
|
||||
// TODO: Implement blast modifiers
|
||||
|
||||
public EntityHealAura(World world){
|
||||
super(world);
|
||||
this.height = 1.0f;
|
||||
@@ -24,7 +29,7 @@ public class EntityHealAura extends EntityMagicConstruct {
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.ticksExisted % 25 == 1){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_SPARKLE, 0.1f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_HEAL_AURA_AMBIENT, 0.1f, 1.0f);
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
@@ -46,9 +51,9 @@ public class EntityHealAura extends EntityMagicConstruct {
|
||||
if(this.getCaster() != null){
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.RADIANT),
|
||||
1 * damageMultiplier);
|
||||
Spells.healing_aura.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier);
|
||||
target.attackEntityFrom(DamageSource.MAGIC, Spells.healing_aura.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier);
|
||||
}
|
||||
|
||||
// Removes knockback
|
||||
@@ -58,16 +63,16 @@ public class EntityHealAura extends EntityMagicConstruct {
|
||||
}
|
||||
|
||||
}else if(target.getHealth() < target.getMaxHealth() && this.ticksExisted % 5 == 0){
|
||||
target.heal(1 * damageMultiplier);
|
||||
target.heal(Spells.healing_aura.getProperty(Spell.HEALTH).floatValue() * 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;
|
||||
float angle = rand.nextFloat() * (float)Math.PI * 2;;
|
||||
ParticleBuilder.create(Type.SPARKLE)
|
||||
.pos(this.posX + radius * Math.cos(angle), this.posY, this.posZ + radius * Math.sin(angle))
|
||||
.pos(this.posX + radius * MathHelper.cos(angle), this.posY, this.posZ + radius * MathHelper.sin(angle))
|
||||
.vel(0, 0.05, 0)
|
||||
.time(48 + this.rand.nextInt(12))
|
||||
.clr(1.0f, 1.0f, brightness)
|
||||
|
||||
@@ -1,35 +1,62 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
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.MoverType;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceSpike extends EntityMagicConstruct {
|
||||
|
||||
private EnumFacing facing;
|
||||
|
||||
public EntityIceSpike(World world){
|
||||
super(world);
|
||||
this.setSize(0.5f, 1.0f);
|
||||
}
|
||||
|
||||
public void setFacing(EnumFacing facing){
|
||||
this.facing = facing;
|
||||
this.setRotation(-facing.getHorizontalAngle(), WizardryUtilities.getPitch(facing));
|
||||
float yaw = (-facing.getHorizontalAngle()) * (float)Math.PI/180;
|
||||
float pitch = (WizardryUtilities.getPitch(facing) - 90) * (float)Math.PI/180;
|
||||
Vec3d min = new Vec3d(-width/2, 0, -width/2).rotatePitch(pitch).rotateYaw(yaw);
|
||||
Vec3d max = new Vec3d(width/2, height, width/2).rotatePitch(pitch).rotateYaw(yaw);
|
||||
this.setEntityBoundingBox(new AxisAlignedBB(this.getPositionVector().add(min), this.getPositionVector().add(max)));
|
||||
}
|
||||
|
||||
public EnumFacing getFacing(){
|
||||
return facing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
double extensionSpeed = 0;
|
||||
|
||||
if(lifetime - this.ticksExisted < 15){
|
||||
this.motionY = -0.01 * (this.ticksExisted - (lifetime - 15));
|
||||
extensionSpeed = -0.01 * (this.ticksExisted - (lifetime - 15));
|
||||
}else if(lifetime - this.ticksExisted < 25){
|
||||
this.motionY = 0;
|
||||
extensionSpeed = 0;
|
||||
}else if(lifetime - this.ticksExisted < 28){
|
||||
this.motionY = 0.25;
|
||||
extensionSpeed = 0.25;
|
||||
}
|
||||
|
||||
this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ);
|
||||
if(facing != null){ // Will probably be null on the client side, but should never be on the server side
|
||||
this.move(MoverType.SELF, this.facing.getXOffset() * extensionSpeed, this.facing.getYOffset() * extensionSpeed,
|
||||
this.facing.getZOffset() * extensionSpeed);
|
||||
}
|
||||
|
||||
if(lifetime - this.ticksExisted == 30) this.playSound(WizardrySounds.SPELL_ICE, 1, 2);
|
||||
if(lifetime - this.ticksExisted == 30) this.playSound(WizardrySounds.ENTITY_ICE_SPIKE_EXTEND, 1, 2);
|
||||
|
||||
if(!this.world.isRemote){
|
||||
for(Object entity : this.world.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox())){
|
||||
@@ -37,8 +64,10 @@ public class EntityIceSpike extends EntityMagicConstruct {
|
||||
// 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));
|
||||
Spells.ice_spikes.getProperty(Spell.DAMAGE).floatValue() * this.damageMultiplier))
|
||||
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost,
|
||||
Spells.ice_spikes.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.ice_spikes.getProperty(Spell.EFFECT_STRENGTH).intValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,4 +75,8 @@ public class EntityIceSpike extends EntityMagicConstruct {
|
||||
super.onUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBrightnessForRender(){
|
||||
return 15728880;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -10,10 +10,15 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityLightningSigil extends EntityMagicConstruct {
|
||||
|
||||
public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets";
|
||||
|
||||
public EntityLightningSigil(World world){
|
||||
super(world);
|
||||
this.height = 0.2f;
|
||||
@@ -42,22 +47,24 @@ public class EntityLightningSigil extends EntityMagicConstruct {
|
||||
|
||||
// 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)){
|
||||
DamageType.SHOCK) : DamageSource.MAGIC, Spells.lightning_sigil.getProperty(Spell.DIRECT_DAMAGE)
|
||||
.floatValue() * damageMultiplier)){
|
||||
|
||||
// Removes knockback
|
||||
target.motionX = velX;
|
||||
target.motionY = velY;
|
||||
target.motionZ = velZ;
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_SPARK, 1.0f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_LIGHTNING_SIGIL_TRIGGER, 1.0f, 1.0f);
|
||||
|
||||
// Secondary chaining effect
|
||||
double seekerRange = 5.0d;
|
||||
double seekerRange = Spells.lightning_sigil.getProperty(Spell.EFFECT_RADIUS).doubleValue();
|
||||
|
||||
List<EntityLivingBase> secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange,
|
||||
target.posX, target.posY + target.height / 2, target.posZ, world);
|
||||
|
||||
for(int j = 0; j < Math.min(secondaryTargets.size(), 3); j++){
|
||||
for(int j = 0; j < Math.min(secondaryTargets.size(),
|
||||
Spells.lightning_sigil.getProperty(SECONDARY_MAX_TARGETS).floatValue()); j++){
|
||||
|
||||
EntityLivingBase secondaryTarget = secondaryTargets.get(j);
|
||||
|
||||
@@ -73,11 +80,12 @@ public class EntityLightningSigil extends EntityMagicConstruct {
|
||||
secondaryTarget.posZ);
|
||||
}
|
||||
|
||||
secondaryTarget.playSound(WizardrySounds.SPELL_SPARK, 1.0F,
|
||||
secondaryTarget.playSound(WizardrySounds.ENTITY_LIGHTNING_SIGIL_TRIGGER, 1.0F,
|
||||
world.rand.nextFloat() * 0.4F + 1.5F);
|
||||
|
||||
secondaryTarget.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), 4);
|
||||
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK),
|
||||
Spells.lightning_sigil.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -89,9 +97,9 @@ public class EntityLightningSigil extends EntityMagicConstruct {
|
||||
|
||||
if(this.world.isRemote && this.rand.nextInt(15) == 0){
|
||||
double radius = 0.5 + rand.nextDouble() * 0.3;
|
||||
double angle = rand.nextDouble() * Math.PI * 2;
|
||||
float angle = rand.nextFloat() * (float)Math.PI * 2;;
|
||||
ParticleBuilder.create(Type.SPARK)
|
||||
.pos(this.posX + radius * Math.cos(angle), this.posY + 0.1, this.posZ + radius * Math.sin(angle))
|
||||
.pos(this.posX + radius * MathHelper.cos(angle), this.posY + 0.1, this.posZ + radius * MathHelper.sin(angle))
|
||||
.spawn(world);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.AllyDesignationSystem;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityOwnable;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* caster UUID, lifetime and damage multiplier are stored here, and lifetime is also synced here.
|
||||
* <p></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 {
|
||||
public abstract class EntityMagicConstruct extends Entity implements IEntityOwnable, 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).
|
||||
*/
|
||||
/** The UUID of the caster. As of Wizardry 4.2, this <b>is</b> synced, and rather than storing the caster
|
||||
* instance via a weak reference, it is fetched from the UUID each time it is needed in
|
||||
* {@link EntityMagicConstruct#getCaster()}. */
|
||||
private UUID casterUUID;
|
||||
|
||||
/**
|
||||
@@ -62,13 +62,6 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityAddi
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
if(this.getCaster() == null && this.casterUUID != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID);
|
||||
if(entity instanceof EntityLivingBase){
|
||||
this.caster = new WeakReference<EntityLivingBase>((EntityLivingBase)entity);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > lifetime && lifetime != -1){
|
||||
this.despawn();
|
||||
}
|
||||
@@ -118,25 +111,51 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityAddi
|
||||
lifetime = data.readInt();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public UUID getOwnerId(){
|
||||
return casterUUID;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Entity getOwner(){
|
||||
return getCaster(); // Delegate to getCaster
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
@Nullable
|
||||
public EntityLivingBase getCaster(){ // Kept despite the above method because it returns an EntityLivingBase
|
||||
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(world, getOwnerId());
|
||||
|
||||
if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen
|
||||
Wizardry.logger.warn("{} has a non-living owner!", this);
|
||||
entity = null;
|
||||
}
|
||||
|
||||
return (EntityLivingBase)entity;
|
||||
}
|
||||
|
||||
public void setCaster(EntityLivingBase caster){
|
||||
this.caster = new WeakReference<EntityLivingBase>(caster);
|
||||
public void setCaster(@Nullable EntityLivingBase caster){
|
||||
this.casterUUID = caster == null ? null : caster.getUniqueID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this construct as the
|
||||
* Shorthand for {@link AllyDesignationSystem#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);
|
||||
return AllyDesignationSystem.isValidTarget(this.getCaster(), target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoundCategory getSoundCategory(){
|
||||
return WizardrySounds.SPELLS;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package electroblob.wizardry.entity.construct;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.spell.Tornado;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -15,15 +17,18 @@ import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.MoverType;
|
||||
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.EnumFacing;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityTornado extends EntityMagicConstruct {
|
||||
|
||||
private double velX, velZ;
|
||||
@@ -45,63 +50,69 @@ public class EntityTornado extends EntityMagicConstruct {
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
double radius = Spells.tornado.getProperty(Spell.EFFECT_RADIUS).doubleValue();
|
||||
|
||||
if(this.ticksExisted % 120 == 1 && world.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);
|
||||
Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_TORNADO_AMBIENT, WizardrySounds.SPELLS, 1.0f, 1.0f, false);
|
||||
}
|
||||
|
||||
this.move(MoverType.SELF, velX, motionY, velZ);
|
||||
|
||||
BlockPos pos = new BlockPos(this);
|
||||
int y = WizardryUtilities.getNearestFloorLevelC(world, pos.up(3), 5);
|
||||
pos = new BlockPos(pos.getX(), y, pos.getZ());
|
||||
Integer y = WizardryUtilities.getNearestSurface(world, pos.up(3), EnumFacing.UP, 5, true, WizardryUtilities.SurfaceCriteria.NOT_AIR_TO_AIR);
|
||||
|
||||
if(this.world.getBlockState(pos).getMaterial() == Material.LAVA){
|
||||
// Fire tornado!
|
||||
this.setFire(5);
|
||||
if(y != null){
|
||||
|
||||
pos = new BlockPos(pos.getX(), y, pos.getZ());
|
||||
|
||||
if(this.world.getBlockState(pos).getMaterial() == Material.LAVA){
|
||||
// Fire tornado!
|
||||
this.setFire(5);
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.world.isRemote){
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(4.0d, this.posX, this.posY,
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
|
||||
for(EntityLivingBase target : targets){
|
||||
|
||||
if(target instanceof EntityPlayer && ((getCaster() instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther)
|
||||
|| ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring))){
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
// TODO: This doesn't seem right...
|
||||
double dx = (this.posX - target.posX > 0 ? 0.5 : -0.5) - (this.posX - target.posX) * 0.125;
|
||||
double dz = (this.posZ - target.posZ > 0 ? 0.5 : -0.5) - (this.posZ - target.posZ) * 0.125;
|
||||
|
||||
if(this.isBurning()){
|
||||
target.setFire(4);
|
||||
target.setFire(4); // Just a fun Easter egg so no properties here!
|
||||
}
|
||||
|
||||
float damage = Spells.tornado.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
if(this.getCaster() != null){
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC),
|
||||
1 * damageMultiplier);
|
||||
target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, getCaster(),
|
||||
DamageType.MAGIC), damage);
|
||||
}else{
|
||||
target.attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier);
|
||||
target.attackEntityFrom(DamageSource.MAGIC, damage);
|
||||
}
|
||||
|
||||
target.motionX = dx;
|
||||
target.motionY = velY + 0.2;
|
||||
target.motionY = velY + Spells.tornado.getProperty(Tornado.UPWARD_ACCELERATION).floatValue();
|
||||
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){
|
||||
WizardryAdvancementTriggers.pig_tornado.triggerFor((EntityPlayer)WizardryUtilities.getRider(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
@@ -114,38 +125,43 @@ public class EntityTornado extends EntityMagicConstruct {
|
||||
|
||||
BlockPos pos1 = new BlockPos(blockX, this.posY + 3, blockZ);
|
||||
|
||||
int blockY = WizardryUtilities.getNearestFloorLevelC(world, pos1, 5) - 1;
|
||||
Integer blockY = WizardryUtilities.getNearestSurface(world, pos1, EnumFacing.UP, 5, true, WizardryUtilities.SurfaceCriteria.NOT_AIR_TO_AIR);
|
||||
|
||||
pos1 = new BlockPos(pos1.getX(), blockY, pos1.getZ());
|
||||
if(blockY != null){
|
||||
|
||||
IBlockState block = this.world.getBlockState(pos1);
|
||||
blockY--;
|
||||
|
||||
// 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 = world.getBiome(pos1).topBlock;
|
||||
}
|
||||
pos1 = new BlockPos(pos1.getX(), blockY, pos1.getZ());
|
||||
|
||||
Wizardry.proxy.spawnTornadoParticle(world, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ,
|
||||
yPos / 3 + 0.5d, 100, block, pos1);
|
||||
Wizardry.proxy.spawnTornadoParticle(world, 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, or snow particles if the block is snow
|
||||
if(this.rand.nextInt(3) == 0){
|
||||
IBlockState block = this.world.getBlockState(pos1);
|
||||
|
||||
Type type = null;
|
||||
|
||||
if(block.getMaterial() == Material.LEAVES) type = Type.LEAF;
|
||||
if(block.getMaterial() == Material.SNOW || block.getMaterial() == Material.CRAFTED_SNOW) type = Type.SNOW;
|
||||
|
||||
if(type != null){
|
||||
double yPos1 = rand.nextDouble() * 8;
|
||||
ParticleBuilder.create(type)
|
||||
.pos(this.posX + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d), this.posY + yPos1,
|
||||
this.posZ + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d))
|
||||
.time(40 + rand.nextInt(10))
|
||||
.spawn(world);
|
||||
// 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 = world.getBiome(pos1).topBlock;
|
||||
}
|
||||
|
||||
Wizardry.proxy.spawnTornadoParticle(world, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ,
|
||||
yPos / 3 + 0.5d, 100, block, pos1);
|
||||
Wizardry.proxy.spawnTornadoParticle(world, 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, or snow particles if the block is snow
|
||||
if(this.rand.nextInt(3) == 0){
|
||||
|
||||
ResourceLocation type = null;
|
||||
|
||||
if(block.getMaterial() == Material.LEAVES) type = Type.LEAF;
|
||||
if(block.getMaterial() == Material.SNOW || block.getMaterial() == Material.CRAFTED_SNOW)
|
||||
type = Type.SNOW;
|
||||
|
||||
if(type != null){
|
||||
double yPos1 = rand.nextDouble() * 8;
|
||||
ParticleBuilder.create(type)
|
||||
.pos(this.posX + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d), this.posY + yPos1,
|
||||
this.posZ + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d))
|
||||
.time(40 + rand.nextInt(10))
|
||||
.spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.event.SpellCastEvent;
|
||||
import electroblob.wizardry.event.SpellCastEvent.Source;
|
||||
import electroblob.wizardry.packet.PacketNPCCastSpell;
|
||||
@@ -18,6 +15,9 @@ import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -106,7 +106,7 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
|
||||
attacker.setContinuousSpell(spell);
|
||||
WizardryPacketHandler.net.sendToAllAround(
|
||||
new PacketNPCCastSpell.Message(attacker.getEntityId(), target == null ? -1 : target.getEntityId(),
|
||||
EnumHand.MAIN_HAND, spell.id(), modifiers),
|
||||
EnumHand.MAIN_HAND, spell, modifiers),
|
||||
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
|
||||
// TODO: Why is this one a 128 block radius, whilst the other one is all in dimension?
|
||||
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
|
||||
@@ -143,8 +143,8 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
|
||||
// ...or the spell is cancelled via events...
|
||||
|| MinecraftForge.EVENT_BUS
|
||||
.post(new SpellCastEvent.Tick(attacker, attacker.getContinuousSpell(), attacker.getModifiers(),
|
||||
Source.NPC, this.continuousSpellDuration - this.continuousSpellTimer))
|
||||
.post(new SpellCastEvent.Tick(Source.NPC, attacker.getContinuousSpell(), attacker,
|
||||
attacker.getModifiers(), this.continuousSpellDuration - this.continuousSpellTimer))
|
||||
// ...or the spell no longer succeeds...
|
||||
|| !attacker.getContinuousSpell().cast(attacker.world, attacker, EnumHand.MAIN_HAND,
|
||||
this.continuousSpellDuration - this.continuousSpellTimer, target, attacker.getModifiers())
|
||||
@@ -159,8 +159,8 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
|
||||
|
||||
}else if(this.continuousSpellDuration - this.continuousSpellTimer == 1){
|
||||
// On the first tick, if the spell did succeed, fire SpellCastEvent.Post.
|
||||
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, attacker.getContinuousSpell(),
|
||||
attacker.getModifiers(), Source.NPC));
|
||||
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, attacker.getContinuousSpell(),
|
||||
attacker, attacker.getModifiers()));
|
||||
}
|
||||
|
||||
}else if(--this.cooldown == 0){
|
||||
@@ -209,7 +209,7 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
|
||||
private boolean attemptCastSpell(Spell spell, SpellModifiers modifiers){
|
||||
|
||||
// If anything stops the spell working at this point, nothing else happens.
|
||||
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(attacker, spell, modifiers, Source.NPC))){
|
||||
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.NPC, spell, attacker, modifiers))){
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -222,16 +222,16 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
|
||||
|
||||
}else{
|
||||
|
||||
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, spell, modifiers, Source.NPC));
|
||||
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, spell, attacker, modifiers));
|
||||
|
||||
// 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;
|
||||
this.cooldown = this.baseCooldown + spell.getCooldown();
|
||||
|
||||
if(spell.doesSpellRequirePacket()){
|
||||
if(spell.requiresPacket()){
|
||||
// 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);
|
||||
EnumHand.MAIN_HAND, spell, modifiers);
|
||||
WizardryPacketHandler.net.sendToDimension(msg, attacker.world.provider.getDimension());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
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;
|
||||
@@ -16,13 +13,15 @@ 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.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@@ -37,22 +36,12 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeakReference<EntityLivingBase> getCasterReference(){
|
||||
return casterReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCasterReference(WeakReference<EntityLivingBase> reference){
|
||||
casterReference = reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getCasterUUID(){
|
||||
public UUID getOwnerId(){
|
||||
return casterUUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCasterUUID(UUID uuid){
|
||||
public void setOwnerId(UUID uuid){
|
||||
this.casterUUID = uuid;
|
||||
}
|
||||
|
||||
@@ -65,13 +54,13 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
|
||||
// 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.
|
||||
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem 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,
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
@@ -142,8 +131,16 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -163,6 +160,6 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
@@ -99,18 +96,19 @@ public class EntityDecoy extends EntitySummonedCreature {
|
||||
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.world.getEntityByID(data.readInt())));
|
||||
}
|
||||
// TESTME: Why was this here? It gets done in ISummonedCreature anyway
|
||||
// @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.world.getEntityByID(data.readInt())));
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
@@ -15,31 +10,18 @@ import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.*;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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.*;
|
||||
import net.minecraft.entity.ai.*;
|
||||
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.nbt.NBTUtil;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
@@ -55,13 +37,21 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntityAdditionalSpawnData {
|
||||
|
||||
private EntityAIAttackSpell<EntityEvilWizard> spellCastingAI = new EntityAIAttackSpell<EntityEvilWizard>(this, 0.5D, 14.0F, 30, 50);
|
||||
private EntityAIAttackSpell<EntityEvilWizard> spellCastingAI = new EntityAIAttackSpell<>(this, 0.5D, 14.0F, 30, 50);
|
||||
|
||||
public int textureIndex = 0;
|
||||
|
||||
public boolean hasTower = false;
|
||||
/** True if this evil wizard was spawned as part of a structure (tower or shrine), false if it spawned naturally. */
|
||||
public boolean hasStructure = false;
|
||||
|
||||
/** Stores the UUIDs of the other evil wizards spawned in the same group, if any. The wizard will not revenge-target
|
||||
* entities whose UUIDs are in this set. This is currently used only for shrines. */
|
||||
public final Set<UUID> groupUUIDs = new HashSet<>();
|
||||
|
||||
/** The entity selector passed into the new AI methods. */
|
||||
protected Predicate<Entity> targetSelector;
|
||||
@@ -95,11 +85,12 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
protected void entityInit(){
|
||||
super.entityInit();
|
||||
this.dataManager.register(HEAL_COOLDOWN, -1);
|
||||
this.dataManager.register(ELEMENT, 0);
|
||||
this.dataManager.register(ELEMENT, -1);
|
||||
}
|
||||
|
||||
@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));
|
||||
@@ -107,34 +98,31 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
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>(){
|
||||
this.targetSelector = entity -> {
|
||||
|
||||
public boolean apply(Entity entity){
|
||||
// If the target is valid and not invisible...
|
||||
if(entity != null && !entity.isInvisible()
|
||||
&& AllyDesignationSystem.isValidTarget(EntityEvilWizard.this, 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.getKey(entity.getClass())))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
|
||||
.contains(EntityList.getKey(entity.getClass()))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
// ... 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.getKey(entity.getClass())))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
|
||||
.contains(EntityList.getKey(entity.getClass()))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
|
||||
0, false, true, this.targetSelector));
|
||||
}
|
||||
|
||||
@@ -154,7 +142,8 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
}
|
||||
|
||||
public Element getElement(){
|
||||
return Element.values()[this.dataManager.get(ELEMENT)];
|
||||
int n = this.dataManager.get(ELEMENT);
|
||||
return n == -1 ? null : Element.values()[n];
|
||||
}
|
||||
|
||||
public void setElement(Element element){
|
||||
@@ -192,6 +181,11 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(@Nullable EntityLivingBase target){
|
||||
if(target == null || !groupUUIDs.contains(target.getUniqueID())) super.setRevengeTarget(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
@@ -225,13 +219,13 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
}
|
||||
}else{
|
||||
if(this.getHealth() < 10){
|
||||
// Wizards heal themseselves more often if they have low health
|
||||
// Wizards heal themselves more often if they have low health
|
||||
this.setHealCooldown(150);
|
||||
}else{
|
||||
this.setHealCooldown(400);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
this.playSound(Spells.heal.getSounds()[0], 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
}
|
||||
}
|
||||
if(healCooldown > 0){
|
||||
@@ -250,8 +244,8 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
// 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.getItem() instanceof ItemSpellBook){
|
||||
Spell spell = Spell.get(stack.getItemDamage());
|
||||
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
|
||||
Spell spell = Spell.byMetadata(stack.getItemDamage());
|
||||
if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){
|
||||
// The set(...) method returns the element that was replaced - neat!
|
||||
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
|
||||
@@ -269,8 +263,9 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
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);
|
||||
nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata())));
|
||||
nbt.setBoolean("hasStructure", this.hasStructure);
|
||||
nbt.setTag("groupUUIDs", NBTExtras.listToNBT(groupUUIDs, NBTUtil::createUUIDTag));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -278,9 +273,10 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
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");
|
||||
this.spells = (List<Spell>)NBTExtras.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
|
||||
(NBTTagInt tag) -> Spell.byMetadata(tag.getInt()));
|
||||
this.hasStructure = nbt.getBoolean("hasStructure");
|
||||
this.groupUUIDs.addAll(NBTExtras.NBTToList(nbt.getTagList("groupUUIDs", NBT.TAG_COMPOUND), NBTUtil::getUUIDFromTag));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -291,7 +287,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
// Evil wizards can only spawn in the specified dimensions
|
||||
for(int id : Wizardry.settings.evilWizardDimensions){
|
||||
for(int id : Wizardry.settings.mobSpawnDimensions){
|
||||
if(this.dimension == id) return super.getCanSpawnHere();
|
||||
}
|
||||
|
||||
@@ -301,27 +297,27 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
@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;
|
||||
return !this.hasStructure;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSoundPitch(){
|
||||
return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F;
|
||||
}
|
||||
// @Override
|
||||
// protected float getSoundPitch(){
|
||||
// return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F;
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_WITCH_AMBIENT;
|
||||
return WizardrySounds.ENTITY_EVIL_WIZARD_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(DamageSource source){
|
||||
return SoundEvents.ENTITY_WITCH_HURT;
|
||||
return WizardrySounds.ENTITY_EVIL_WIZARD_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_WITCH_DEATH;
|
||||
return WizardrySounds.ENTITY_EVIL_WIZARD_DEATH;
|
||||
}
|
||||
|
||||
// Although it *looks* like this is still called, in actual fact the only method that calls it is overridden in
|
||||
@@ -340,7 +336,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
// 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);
|
||||
this.spells.get(1 + rand.nextInt(this.spells.size() - 1)).metadata()), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -355,17 +351,19 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
|
||||
textureIndex = this.rand.nextInt(6);
|
||||
|
||||
if(rand.nextBoolean()){
|
||||
this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]);
|
||||
}else{
|
||||
this.setElement(Element.MAGIC);
|
||||
if(getElement() == null){
|
||||
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)));
|
||||
this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot)));
|
||||
}
|
||||
|
||||
// Default chance is 0.085f, for reference.
|
||||
@@ -375,12 +373,12 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
// All wizards know magic missile, even if it is disabled.
|
||||
spells.add(Spells.magic_missile);
|
||||
|
||||
Tier maxTier = EntityWizard.populateSpells(spells, element, 3, rand);
|
||||
Tier maxTier = EntityWizard.populateSpells(spells, element, hasStructure, 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)));
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryItems.getWand(tier, element)));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
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.DamageSource;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityHuskMinion extends EntityZombieMinion {
|
||||
|
||||
/** Creates a new husk minion in the given world. */
|
||||
public EntityHuskMinion(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldBurnInDay(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_HUSK_AMBIENT; }
|
||||
@Override protected SoundEvent getHurtSound(DamageSource damageSourceIn){ return SoundEvents.ENTITY_HUSK_HURT; }
|
||||
@Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_HUSK_DEATH; }
|
||||
@Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_HUSK_STEP; }
|
||||
|
||||
@Override
|
||||
public boolean attackEntityAsMob(Entity target){
|
||||
|
||||
boolean flag = super.attackEntityAsMob(target);
|
||||
|
||||
if(flag && this.getHeldItemMainhand().isEmpty() && target instanceof EntityLivingBase){
|
||||
float f = this.world.getDifficultyForLocation(new BlockPos(this)).getAdditionalDifficulty();
|
||||
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.HUNGER, 140 * (int)f));
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
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;
|
||||
@@ -10,15 +7,9 @@ import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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.ai.*;
|
||||
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.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
@@ -27,22 +18,22 @@ 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.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
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; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
/** Creates a new ice giant in the given world. */
|
||||
public EntityIceGiant(World world){
|
||||
@@ -91,7 +82,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_GIANT_DESPAWN, 1.0f, 1.0f);
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@@ -126,7 +117,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
|
||||
|
||||
this.applyEnchantments(this, target);
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_IRONGOLEM_ATTACK, 1.0F, 1.0F);
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_GIANT_ATTACK, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,8 +151,16 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -182,7 +181,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
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.ai.*;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
@@ -66,7 +63,7 @@ public class EntityIceWraith extends EntityBlazeMinion {
|
||||
}
|
||||
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 0.3F + this.rand.nextFloat() / 4,
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_WRAITH_AMBIENT, 0.3F + this.rand.nextFloat() / 4,
|
||||
this.rand.nextFloat() * 0.7F + 1.4F);
|
||||
}
|
||||
|
||||
@@ -181,14 +178,24 @@ public class EntityIceWraith extends EntityBlazeMinion {
|
||||
@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
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
// Only spawns in the specified dimensions
|
||||
for(int id : Wizardry.settings.mobSpawnDimensions){
|
||||
if(this.dimension == id) return super.getCanSpawnHere() && this.isValidLightLevel();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* with a one-liner call to WizardryLoot.iceShard.cast(...) and the removal of redundant local variables.
|
||||
*/
|
||||
static class AIIceShardAttack extends EntityAIBase {
|
||||
|
||||
@@ -222,6 +229,7 @@ public class EntityIceWraith extends EntityBlazeMinion {
|
||||
public void updateTask(){
|
||||
--this.attackTime;
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
if(entitylivingbase == null) return; // Dynamic stealth breaks things, let's un-break them
|
||||
double d0 = this.blaze.getDistanceSq(entitylivingbase);
|
||||
|
||||
if(d0 < 4.0D){
|
||||
@@ -251,7 +259,6 @@ public class EntityIceWraith extends EntityBlazeMinion {
|
||||
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
|
||||
Spells.ice_shard.cast(this.blaze.world, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase,
|
||||
new SpellModifiers());
|
||||
// TODO: Decide if an event should be fired here. I'm guessing no.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
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.ai.*;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
@@ -75,9 +72,21 @@ public class EntityLightningWraith extends EntityBlazeMinion {
|
||||
return this.getFlag(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
// Only spawns in the specified dimensions, during thunderstorms
|
||||
if(!world.isThundering()) return false;
|
||||
|
||||
for(int id : Wizardry.settings.mobSpawnDimensions){
|
||||
if(this.dimension == id) return super.getCanSpawnHere() && this.isValidLightLevel();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* with a one-liner call to WizardryLoot.arc.cast(...) and the removal of redundant local variables.
|
||||
*/
|
||||
static class AILightningAttack extends EntityAIBase {
|
||||
|
||||
@@ -111,6 +120,7 @@ public class EntityLightningWraith extends EntityBlazeMinion {
|
||||
public void updateTask(){
|
||||
--this.attackTime;
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
if(entitylivingbase == null) return; // Dynamic stealth breaks things, let's un-break them
|
||||
double d0 = this.blaze.getDistanceSq(entitylivingbase);
|
||||
|
||||
if(d0 < 4.0D){
|
||||
@@ -139,7 +149,6 @@ public class EntityLightningWraith extends EntityBlazeMinion {
|
||||
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.world, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
|
||||
// TODO: Decide if an event should be fired here. I'm guessing no.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
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.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
@@ -22,21 +17,21 @@ import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.UUID;
|
||||
|
||||
/** 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; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
public EntityMagicSlime(World world){
|
||||
super(world);
|
||||
@@ -57,7 +52,7 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
|
||||
super(world);
|
||||
this.setPosition(target.posX, target.posY, target.posZ);
|
||||
this.startRiding(target);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.setOwnerId(caster.getUniqueID());
|
||||
this.setSlimeSize(2, false); // Needs to be called before setting the experience value to 0
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
@@ -83,8 +78,8 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
|
||||
this.world.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);
|
||||
this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_SPLAT, 2.5f, 0.6f);
|
||||
this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_EXPLODE, 1.0f, 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,7 +122,7 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
|
||||
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.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_ATTACK, 1.0f, 1.0f);
|
||||
this.squishAmount = 0.5F;
|
||||
}
|
||||
}else{
|
||||
@@ -174,7 +169,10 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
@@ -13,7 +11,6 @@ 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.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
@@ -22,6 +19,9 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
private double AISpeed = 0.5;
|
||||
@@ -97,17 +97,17 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
return WizardrySounds.ENTITY_PHOENIX_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(DamageSource source){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
return WizardrySounds.ENTITY_PHOENIX_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
return WizardrySounds.ENTITY_PHOENIX_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,9 +144,9 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
|
||||
public void onLivingUpdate(){
|
||||
|
||||
// Makes the phoenix hover.
|
||||
int floorLevel = WizardryUtilities.getNearestFloorLevel(world, new BlockPos(this), 4);
|
||||
Integer floorLevel = WizardryUtilities.getNearestFloor(world, new BlockPos(this), 4);
|
||||
|
||||
if(this.posY - floorLevel > 3){
|
||||
if(floorLevel == null || this.posY - floorLevel > 3){
|
||||
this.motionY = -0.1;
|
||||
}else if(this.posY - floorLevel < 2){
|
||||
this.motionY = 0.1;
|
||||
@@ -156,13 +156,13 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
|
||||
|
||||
// Living sound
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 1.0F + this.rand.nextFloat(),
|
||||
this.playSound(WizardrySounds.ENTITY_PHOENIX_BURN, 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);
|
||||
this.playSound(WizardrySounds.ENTITY_PHOENIX_FLAP, 1.0F, 1.0f);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 2; i++){
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
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.ai.*;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
@@ -25,6 +18,9 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EntityShadowWraith extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
// TODO: This currently doesn't fly like it used to. Should it, or does it not matter?
|
||||
@@ -96,17 +92,17 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
return WizardrySounds.ENTITY_SHADOW_WRAITH_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(DamageSource source){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
return WizardrySounds.ENTITY_SHADOW_WRAITH_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
return WizardrySounds.ENTITY_SHADOW_WRAITH_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,7 +131,7 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.0F + this.rand.nextFloat(),
|
||||
this.playSound(WizardrySounds.ENTITY_SHADOW_WRAITH_NOISE, 1.0F + this.rand.nextFloat(),
|
||||
this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
@@ -20,22 +17,26 @@ 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.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntitySilverfishMinion extends EntitySilverfish implements ISummonedCreature {
|
||||
|
||||
public static final int MAX_GENERATIONS = 5;
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
private UUID casterUUID;
|
||||
|
||||
private int generation = 1;
|
||||
|
||||
// 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; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
/** Creates a new silverfish minion in the given world. */
|
||||
public EntitySilverfishMinion(World world){
|
||||
@@ -50,7 +51,7 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
|
||||
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,
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
|
||||
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.world.isRemote){
|
||||
if(!this.world.isRemote && generation < MAX_GENERATIONS){
|
||||
// Summons 1-4 more silverfish
|
||||
int alliesToSummon = rand.nextInt(4) + 1;
|
||||
|
||||
@@ -108,6 +109,7 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
|
||||
silverfish.setPosition(victim.posX, victim.posY, victim.posZ);
|
||||
silverfish.setCaster(this.getCaster());
|
||||
silverfish.setLifetime(this.getLifetime());
|
||||
silverfish.generation = this.generation + 1;
|
||||
this.world.spawnEntity(silverfish);
|
||||
}
|
||||
}
|
||||
@@ -129,12 +131,14 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
nbttagcompound.setInteger("generation", this.generation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.readNBTDelegate(nbttagcompound);
|
||||
this.generation = nbttagcompound.getInteger("generation");
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
@@ -144,8 +148,16 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -166,6 +178,6 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
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 electroblob.wizardry.util.WizardryUtilities.Operations;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
@@ -14,37 +9,39 @@ 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.AbstractSkeleton;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemBow;
|
||||
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.*;
|
||||
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 EntitySkeletonMinion extends EntitySkeleton implements ISummonedCreature {
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
// Extends AbstractSkeleton because EntitySkeleton drops skulls, which we don't want
|
||||
public class EntitySkeletonMinion extends AbstractSkeleton implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
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; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
/** Creates a new skeleton minion in the given world. */
|
||||
public EntitySkeletonMinion(World world){
|
||||
@@ -55,13 +52,13 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
|
||||
// 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.
|
||||
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem 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,
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
@@ -76,7 +73,7 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
|
||||
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));
|
||||
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT));
|
||||
|
||||
if(this.rand.nextFloat() < 0.05F){
|
||||
this.setLeftHanded(true);
|
||||
@@ -98,6 +95,12 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
// Since we're extending AbstractSkeleton these aren't set by the superclass like normal
|
||||
@Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_SKELETON_AMBIENT; }
|
||||
@Override protected SoundEvent getHurtSound(DamageSource source){ return SoundEvents.ENTITY_SKELETON_HURT; }
|
||||
@Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_SKELETON_DEATH; }
|
||||
@Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_SKELETON_STEP; }
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
@@ -161,8 +164,16 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -183,6 +194,6 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities.Operations;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
@@ -27,20 +25,19 @@ import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
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; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
/** Creates a new spider minion in the given world. */
|
||||
public EntitySpiderMinion(World world){
|
||||
@@ -51,7 +48,7 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
|
||||
// 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.
|
||||
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account.
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
super.initEntityAI();
|
||||
@@ -70,7 +67,7 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
|
||||
|
||||
// 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));
|
||||
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT));
|
||||
|
||||
if(this.rand.nextFloat() < 0.05F){
|
||||
this.setLeftHanded(true);
|
||||
@@ -163,8 +160,16 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -185,6 +190,6 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.item.ISpellCastingItem;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
@@ -16,7 +15,6 @@ 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.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
@@ -34,6 +32,10 @@ public class EntitySpiritHorse extends EntityHorse {
|
||||
|
||||
private int idleTimer = 0;
|
||||
|
||||
private int dispelTimer = 0;
|
||||
|
||||
private static final int DISPEL_TIME = 10;
|
||||
|
||||
public EntitySpiritHorse(World par1World){
|
||||
super(par1World);
|
||||
}
|
||||
@@ -93,19 +95,13 @@ public class EntitySpiritHorse extends EntityHorse {
|
||||
|
||||
// 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.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
|
||||
if(itemstack.getItem() instanceof ISpellCastingItem && this.getOwner() == player && player.isSneaking()){
|
||||
// Prevents accidental double clicking.
|
||||
if(this.ticksExisted > 20){
|
||||
|
||||
this.dispelTimer++;
|
||||
|
||||
this.spawnAppearParticles();
|
||||
|
||||
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.playSound(WizardrySounds.ENTITY_SPIRIT_HORSE_VANISH, 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;
|
||||
}
|
||||
@@ -124,17 +120,6 @@ public class EntitySpiritHorse extends EntityHorse {
|
||||
}
|
||||
}
|
||||
|
||||
@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(){
|
||||
|
||||
@@ -148,11 +133,21 @@ public class EntitySpiritHorse extends EntityHorse {
|
||||
}
|
||||
}
|
||||
|
||||
public float getOpacity(){
|
||||
return 1 - (float)dispelTimer/DISPEL_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(dispelTimer > 0){
|
||||
if(dispelTimer++ > DISPEL_TIME){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a dust particle effect
|
||||
if(this.world.isRemote){
|
||||
double x = this.posX - this.width / 2 + this.rand.nextFloat() * width;
|
||||
@@ -170,17 +165,9 @@ public class EntitySpiritHorse extends EntityHorse {
|
||||
|
||||
if(this.idleTimer > 200){
|
||||
|
||||
if(this.world.isRemote){
|
||||
this.spawnAppearParticles();
|
||||
}
|
||||
this.playSound(WizardrySounds.ENTITY_SPIRIT_HORSE_VANISH, 0.7F, rand.nextFloat() * 0.4F + 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();
|
||||
this.dispelTimer++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +200,7 @@ public class EntitySpiritHorse extends EntityHorse {
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getOwner() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.item.ISpellCastingItem;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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.ai.*;
|
||||
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.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
@@ -37,9 +25,12 @@ import net.minecraft.world.World;
|
||||
*/
|
||||
public class EntitySpiritWolf extends EntityWolf {
|
||||
|
||||
public EntitySpiritWolf(World par1World){
|
||||
private int dispelTimer = 0;
|
||||
|
||||
super(par1World);
|
||||
private static final int DISPEL_TIME = 10;
|
||||
|
||||
public EntitySpiritWolf(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
@@ -60,18 +51,6 @@ public class EntitySpiritWolf extends EntityWolf {
|
||||
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
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata){
|
||||
|
||||
@@ -92,12 +71,22 @@ public class EntitySpiritWolf extends EntityWolf {
|
||||
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).clr(0.8f, 0.8f, 1.0f).spawn(world);
|
||||
}
|
||||
}
|
||||
|
||||
public float getOpacity(){
|
||||
return 1 - (float)dispelTimer/DISPEL_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(dispelTimer > 0){
|
||||
if(dispelTimer++ > DISPEL_TIME){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a dust particle effect
|
||||
if(this.world.isRemote){
|
||||
double x = this.posX - this.width / 2 + this.rand.nextFloat() * width;
|
||||
@@ -116,21 +105,13 @@ public class EntitySpiritWolf extends EntityWolf {
|
||||
|
||||
// Allows the owner (but not other players) to dispel the spirit wolf using a
|
||||
// wand.
|
||||
if(stack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
|
||||
if(stack.getItem() instanceof ISpellCastingItem && this.getOwner() == player && player.isSneaking()){
|
||||
// Prevents accidental double clicking.
|
||||
if(this.ticksExisted > 20){
|
||||
|
||||
if(this.world.isRemote){
|
||||
this.spawnAppearParticles();
|
||||
}
|
||||
this.dispelTimer++;
|
||||
|
||||
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.playSound(WizardrySounds.ENTITY_SPIRIT_WOLF_VANISH, 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;
|
||||
@@ -181,7 +162,7 @@ public class EntitySpiritWolf extends EntityWolf {
|
||||
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;
|
||||
return Wizardry.settings.summonedCreatureNames && getOwner() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
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.ai.*;
|
||||
import net.minecraft.entity.effect.EntityLightningBolt;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
@@ -25,6 +17,9 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EntityStormElemental extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
private double AISpeed = 1.0;
|
||||
@@ -90,17 +85,17 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
return WizardrySounds.ENTITY_STORM_ELEMENTAL_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(DamageSource source){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
return WizardrySounds.ENTITY_STORM_ELEMENTAL_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
return WizardrySounds.ENTITY_STORM_ELEMENTAL_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,11 +113,11 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(this.ticksExisted % 120 == 1){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f);
|
||||
this.playSound(WizardrySounds.ENTITY_STORM_ELEMENTAL_WIND, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.ENTITY_BLAZE_BURN, 1.0F + this.rand.nextFloat(),
|
||||
this.playSound(WizardrySounds.ENTITY_STORM_ELEMENTAL_BURN, 1.0F + this.rand.nextFloat(),
|
||||
this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import net.minecraft.entity.projectile.EntityArrow;
|
||||
import net.minecraft.entity.projectile.EntityTippedArrow;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityStrayMinion extends EntitySkeletonMinion {
|
||||
|
||||
/** Creates a new stray minion in the given world. */
|
||||
public EntityStrayMinion(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_STRAY_AMBIENT; }
|
||||
@Override protected SoundEvent getHurtSound(DamageSource source){ return SoundEvents.ENTITY_STRAY_HURT; }
|
||||
@Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_STRAY_DEATH; }
|
||||
@Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_STRAY_STEP; }
|
||||
|
||||
@Override
|
||||
protected EntityArrow getArrow(float distanceFactor){
|
||||
|
||||
EntityArrow entityarrow = super.getArrow(distanceFactor);
|
||||
|
||||
if(entityarrow instanceof EntityTippedArrow){
|
||||
((EntityTippedArrow)entityarrow).addEffect(new PotionEffect(MobEffects.SLOWNESS, 600));
|
||||
}
|
||||
|
||||
return entityarrow;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
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;
|
||||
@@ -14,8 +11,11 @@ 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.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -29,8 +29,7 @@ import net.minecraft.world.World;
|
||||
public abstract class EntitySummonedCreature extends EntityCreature implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@@ -45,22 +44,12 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeakReference<EntityLivingBase> getCasterReference(){
|
||||
return casterReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCasterReference(WeakReference<EntityLivingBase> reference){
|
||||
casterReference = reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getCasterUUID(){
|
||||
public UUID getOwnerId(){
|
||||
return casterUUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCasterUUID(UUID uuid){
|
||||
public void setOwnerId(UUID uuid){
|
||||
this.casterUUID = uuid;
|
||||
}
|
||||
|
||||
@@ -122,8 +111,16 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -144,7 +141,7 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
// Specific to EntitySummonedCreature, remove if copying
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.monster.EntityVex;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
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.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityVexMinion extends EntityVex implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = -1;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
/** Creates a new vex minion in the given world. */
|
||||
public EntityVexMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
// ISummonedCreature overrides
|
||||
@Override
|
||||
public void setCaster(@Nullable EntityLivingBase caster){
|
||||
// Integrates the summoned creature caster system with the (subtly different) vex owner system for NPC casters
|
||||
ISummonedCreature.super.setCaster(caster);
|
||||
if(caster instanceof EntityLiving) this.setOwner((EntityLiving)caster);
|
||||
}
|
||||
|
||||
// EntityVex overrides
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
super.initEntityAI();
|
||||
this.targetTasks.taskEntries.clear();
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
|
||||
0, false, false, 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.world.isRemote){
|
||||
for(int i = 0; i < 15; i++){
|
||||
ParticleBuilder.create(Type.DARK_MAGIC)
|
||||
.pos(this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat())
|
||||
.clr(0.3f, 0.3f, 0.3f)
|
||||
.spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand){
|
||||
// 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) || super.processInteract(player, hand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.readNBTDelegate(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 getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@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.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
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 electroblob.wizardry.util.WizardryUtilities.Operations;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
@@ -31,22 +26,24 @@ 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;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
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; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
/** Creates a new wither skeleton minion in the given world. */
|
||||
public EntityWitherSkeletonMinion(World world){
|
||||
@@ -57,7 +54,7 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
|
||||
// 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.
|
||||
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account.
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
super.initEntityAI();
|
||||
@@ -78,7 +75,7 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
|
||||
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));
|
||||
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT));
|
||||
|
||||
if(this.rand.nextFloat() < 0.05F){
|
||||
this.setLeftHanded(true);
|
||||
@@ -171,8 +168,16 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -193,6 +198,6 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,37 @@
|
||||
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.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.data.WizardData;
|
||||
import electroblob.wizardry.event.DiscoverSpellEvent;
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.misc.WildcardTradeList;
|
||||
import electroblob.wizardry.registry.*;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WandHelper;
|
||||
import electroblob.wizardry.util.WildcardTradeList;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import electroblob.wizardry.util.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityCreature;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.IMerchant;
|
||||
import net.minecraft.entity.INpc;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
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.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest2;
|
||||
import net.minecraft.entity.*;
|
||||
import net.minecraft.entity.ai.*;
|
||||
import net.minecraft.entity.monster.IMob;
|
||||
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.nbt.*;
|
||||
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.ResourceLocation;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
@@ -73,6 +40,7 @@ import net.minecraft.village.MerchantRecipeList;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
@@ -83,6 +51,9 @@ import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
@Mod.EventBusSubscriber
|
||||
public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISpellCaster, IEntityAdditionalSpawnData {
|
||||
|
||||
@@ -150,7 +121,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
// If the target is valid and not invisible...
|
||||
if(entity != null && !entity.isInvisible()
|
||||
&& WizardryUtilities.isValidTarget(EntityWizard.this, entity)){
|
||||
&& AllyDesignationSystem.isValidTarget(EntityWizard.this, entity)){
|
||||
|
||||
// ... and is a mob, a summoned creature ...
|
||||
if((entity instanceof IMob || entity instanceof ISummonedCreature
|
||||
@@ -247,7 +218,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
// Copied from EntityVillager
|
||||
if(!this.world.isRemote && this.livingSoundTime > -this.getTalkInterval() + 20){
|
||||
this.livingSoundTime = -this.getTalkInterval();
|
||||
this.playSound(stack.isEmpty() ? SoundEvents.ENTITY_VILLAGER_NO : SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch());
|
||||
this.playSound(stack.isEmpty() ? WizardrySounds.ENTITY_WIZARD_NO : WizardrySounds.ENTITY_WIZARD_YES, this.getSoundVolume(), this.getSoundPitch());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,8 +238,14 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
// Apparently nothing goes here, and nothing's here in EntityVillager either...
|
||||
}
|
||||
|
||||
// TESTME: Should this be getName instead?
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
|
||||
if(this.hasCustomName()){
|
||||
return super.getDisplayName();
|
||||
}
|
||||
|
||||
return this.getElement().getWizardName();
|
||||
}
|
||||
|
||||
@@ -276,6 +253,21 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
protected boolean canDespawn(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return this.isTrading() ? WizardrySounds.ENTITY_WIZARD_TRADING : WizardrySounds.ENTITY_WIZARD_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(DamageSource source){
|
||||
return WizardrySounds.ENTITY_WIZARD_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return WizardrySounds.ENTITY_WIZARD_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
@@ -301,14 +293,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
// Heal particles TODO: Change this so it uses the heal spell directly
|
||||
if(world.isRemote){
|
||||
for(int i=0; i<10; i++){
|
||||
double x = (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 y = (double)((float)this.posY - 0.5F + rand.nextFloat());
|
||||
double z = (double)((float)this.posZ + rand.nextFloat() * 2 - 1.0F);
|
||||
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1F, 0).clr(1, 1, 0.3f).spawn(world);
|
||||
}
|
||||
ParticleBuilder.spawnHealParticles(world, this);
|
||||
}else{
|
||||
if(this.getHealth() < 10){
|
||||
// Wizards heal themseselves more often if they have low health
|
||||
@@ -317,7 +302,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
this.setHealCooldown(400);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
this.playSound(Spells.heal.getSounds()[0], 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,8 +355,8 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
// 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.getItem() instanceof ItemSpellBook){
|
||||
Spell spell = Spell.get(stack.getItemDamage());
|
||||
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
|
||||
Spell spell = Spell.byMetadata(stack.getItemDamage());
|
||||
if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){
|
||||
// The set(...) method returns the element that was replaced - neat!
|
||||
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
|
||||
@@ -407,11 +392,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
nbt.setInteger("element", this.getElement().ordinal());
|
||||
nbt.setInteger("skin", this.textureIndex);
|
||||
nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id())));
|
||||
nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata())));
|
||||
|
||||
if(this.towerBlocks != null && this.towerBlocks.size() > 0){
|
||||
nbt.setTag("towerBlocks",
|
||||
WizardryUtilities.listToNBT(this.towerBlocks, pos -> new NBTTagLong(pos.toLong())));
|
||||
nbt.setTag("towerBlocks", NBTExtras.listToNBT(this.towerBlocks, NBTUtil::createPosTag));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,11 +411,17 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
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.spells = (List<Spell>)NBTExtras.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
|
||||
(NBTTagInt tag) -> Spell.byMetadata(tag.getInt()));
|
||||
|
||||
this.towerBlocks = new HashSet<BlockPos>(WizardryUtilities.NBTToList(
|
||||
nbt.getTagList("towerBlocks", NBT.TAG_LONG), (NBTTagLong tag) -> BlockPos.fromLong(tag.getLong())));
|
||||
NBTTagList tagList = nbt.getTagList("towerBlocks", NBT.TAG_LONG);
|
||||
if(!tagList.isEmpty()){
|
||||
this.towerBlocks = new HashSet<>(NBTExtras.NBTToList(tagList, NBTUtil::getPosFromTag));
|
||||
}else{
|
||||
// Fallback to old packed long format
|
||||
this.towerBlocks = new HashSet<>(NBTExtras.NBTToList(nbt.getTagList("towerBlocks", NBT.TAG_LONG),
|
||||
(NBTTagLong tag) -> BlockPos.fromLong(tag.getLong())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -439,20 +429,42 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
merchantrecipe.incrementToolUses();
|
||||
this.livingSoundTime = -this.getTalkInterval();
|
||||
this.playSound(SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch());
|
||||
this.playSound(WizardrySounds.ENTITY_WIZARD_YES, this.getSoundVolume(), this.getSoundPitch());
|
||||
|
||||
// Achievements
|
||||
if(this.getCustomer() != null){
|
||||
|
||||
// Achievements
|
||||
WizardryAdvancementTriggers.wizard_trade.triggerFor(this.getCustomer());
|
||||
|
||||
if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook
|
||||
&& Spell.get(merchantrecipe.getItemToSell().getItemDamage()).tier == Tier.MASTER){
|
||||
WizardryAdvancementTriggers.buy_master_spell.triggerFor(this.getCustomer());
|
||||
if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook){
|
||||
|
||||
Spell spell = Spell.byMetadata(merchantrecipe.getItemToSell().getItemDamage());
|
||||
|
||||
if(spell.getTier() == Tier.MASTER) WizardryAdvancementTriggers.buy_master_spell.triggerFor(this.getCustomer());
|
||||
|
||||
// Spell discovery (a lot of this is the same as in the event handler)
|
||||
WizardData data = WizardData.get(this.getCustomer());
|
||||
|
||||
if(data != null){
|
||||
|
||||
if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(this.getCustomer(), spell,
|
||||
DiscoverSpellEvent.Source.PURCHASE)) && data.discoverSpell(spell)){
|
||||
|
||||
data.sync();
|
||||
|
||||
if(!world.isRemote && !this.getCustomer().isCreative() && Wizardry.settings.discoveryMode){
|
||||
// Sound and text only happen server-side, in survival, with discovery mode on
|
||||
WizardryUtilities.playSoundAtPlayer(this.getCustomer(), WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
|
||||
this.getCustomer().sendMessage(new TextComponentTranslation("spell.discover",
|
||||
spell.getNameForTranslationFormatted()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Changed to a 4 in 5 chance of unlocking a new recipe.
|
||||
if(this.rand.nextInt(5) > 0){
|
||||
if(this.rand.nextInt(5) > 0 || ItemArtefact.isArtefactActive(customer, WizardryItems.charm_haggler)){
|
||||
this.timeUntilReset = 40;
|
||||
this.updateRecipes = true;
|
||||
|
||||
@@ -500,7 +512,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
boolean itemAlreadySold = true;
|
||||
|
||||
Tier tier = Tier.BASIC;
|
||||
Tier tier = Tier.NOVICE;
|
||||
|
||||
while(itemAlreadySold){
|
||||
|
||||
@@ -515,7 +527,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
double tierIncreaseChance = 0.5 + 0.04 * (Math.max(this.trades.size() - 4, 0));
|
||||
|
||||
tier = Tier.BASIC;
|
||||
tier = Tier.NOVICE;
|
||||
|
||||
if(rand.nextDouble() < tierIncreaseChance){
|
||||
tier = Tier.APPRENTICE;
|
||||
@@ -545,8 +557,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
// Don't know how it can ever be empty here, but it's a failsafe.
|
||||
if(itemToSell.isEmpty()) return;
|
||||
|
||||
merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier),
|
||||
new ItemStack(WizardryItems.magic_crystal, tier.ordinal() * 3 + 1 + rand.nextInt(4)), itemToSell));
|
||||
ItemStack secondItemToBuy = tier == Tier.MASTER ? new ItemStack(WizardryItems.astral_diamond)
|
||||
: new ItemStack(WizardryItems.magic_crystal, tier.ordinal() * 3 + 1 + rand.nextInt(4));
|
||||
|
||||
merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier), secondItemToBuy, itemToSell));
|
||||
}
|
||||
|
||||
Collections.shuffle(merchantrecipelist);
|
||||
@@ -555,27 +569,31 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
this.trades = new WildcardTradeList();
|
||||
}
|
||||
|
||||
for(int j1 = 0; j1 < merchantrecipelist.size(); ++j1){
|
||||
this.trades.add(merchantrecipelist.get(j1));
|
||||
}
|
||||
this.trades.addAll(merchantrecipelist);
|
||||
}
|
||||
|
||||
// TODO: Switch all of this over to some kind of loot pool system?
|
||||
|
||||
private ItemStack getRandomPrice(Tier tier){
|
||||
ItemStack itemstack = ItemStack.EMPTY;
|
||||
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;
|
||||
|
||||
Map<ResourceLocation, Integer> map = Wizardry.settings.currencyItems;
|
||||
// This isn't that efficient but it's not called very often really so it doesn't matter
|
||||
ResourceLocation itemName = map.keySet().toArray(new ResourceLocation[0])[rand.nextInt(map.size())];
|
||||
Item item = Item.REGISTRY.getObject(itemName);
|
||||
int value;
|
||||
|
||||
if(item == null){
|
||||
Wizardry.logger.warn("Invalid item in currency items: {}", itemName);
|
||||
item = Items.EMERALD; // Fallback item
|
||||
value = 6;
|
||||
}else{
|
||||
value = map.get(itemName);
|
||||
}
|
||||
return itemstack;
|
||||
|
||||
// ((tier.ordinal() + 1) * 16 + rand.nextInt(6)) gives a 'value' for the item being bought
|
||||
// This is then divided by the value of the currency item to give a price
|
||||
// The absolute maximum stack size that can result from this calculation (with value = 1) is 64.
|
||||
return new ItemStack(item, (8 + tier.ordinal() * 16 + rand.nextInt(9)) / value);
|
||||
}
|
||||
|
||||
private ItemStack getRandomItemOfTier(Tier tier){
|
||||
@@ -583,32 +601,36 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
int randomiser;
|
||||
|
||||
// All enabled spells of the given tier
|
||||
List<Spell> spells = Spell.getSpells(new Spell.TierElementFilter(tier, null));
|
||||
List<Spell> spells = Spell.getSpells(new Spell.TierElementFilter(tier, null, SpellProperties.Context.TRADES));
|
||||
// All enabled spells of the given tier that match this wizard's element
|
||||
List<Spell> specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement()));
|
||||
List<Spell> specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement(), SpellProperties.Context.TRADES));
|
||||
|
||||
// Wizards don't sell scrolls
|
||||
spells.removeIf(s -> !s.isEnabled(SpellProperties.Context.BOOK));
|
||||
specialismSpells.removeIf(s -> !s.isEnabled(SpellProperties.Context.BOOK));
|
||||
|
||||
// This code is sooooooo much neater with the new filter system!
|
||||
switch(tier){
|
||||
|
||||
case BASIC:
|
||||
case NOVICE:
|
||||
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());
|
||||
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata());
|
||||
}
|
||||
}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()));
|
||||
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(
|
||||
WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,18 +641,18 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
// 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());
|
||||
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata());
|
||||
}
|
||||
}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()));
|
||||
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(
|
||||
WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}else if(randomiser < 8){
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 1);
|
||||
@@ -639,10 +661,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
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));
|
||||
return new ItemStack(WizardryItems.getArmour(this.getElement(), slot));
|
||||
}else{
|
||||
return new ItemStack(
|
||||
WizardryUtilities.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot));
|
||||
WizardryItems.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot));
|
||||
}
|
||||
}else{
|
||||
// Don't need to check for discovery mode here since it is done above
|
||||
@@ -656,18 +678,18 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
// 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());
|
||||
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata());
|
||||
}
|
||||
}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()));
|
||||
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(
|
||||
WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}else if(randomiser < 8){
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 2);
|
||||
@@ -684,12 +706,12 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
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());
|
||||
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
|
||||
|
||||
}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()));
|
||||
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.master_wand);
|
||||
}
|
||||
@@ -718,7 +740,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
// Adds armour.
|
||||
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
|
||||
this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot)));
|
||||
this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot)));
|
||||
}
|
||||
|
||||
// Default chance is 0.085f, for reference.
|
||||
@@ -728,11 +750,16 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
// All wizards know magic missile, even if it is disabled.
|
||||
spells.add(Spells.magic_missile);
|
||||
|
||||
Tier maxTier = populateSpells(spells, element, 3, rand);
|
||||
Tier maxTier = populateSpells(spells, element, false, 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)));
|
||||
ItemStack wand = new ItemStack(WizardryItems.getWand(maxTier, element));
|
||||
ArrayList<Spell> list = new ArrayList<>(spells);
|
||||
list.add(Spells.heal);
|
||||
WandHelper.setSpells(wand, list.toArray(new Spell[5]));
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, wand);
|
||||
|
||||
this.setHealCooldown(50);
|
||||
|
||||
return livingdata;
|
||||
}
|
||||
@@ -747,14 +774,14 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
* @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){
|
||||
static Tier populateSpells(List<Spell> spells, Element e, boolean master, int n, Random random){
|
||||
|
||||
// This is the tier of the highest tier spell added.
|
||||
Tier maxTier = Tier.BASIC;
|
||||
Tier maxTier = Tier.NOVICE;
|
||||
|
||||
List<Spell> npcSpells = Spell.getSpells(Spell.npcSpells);
|
||||
|
||||
for(int i = 0; i < 3; i++){
|
||||
for(int i = 0; i < n; i++){
|
||||
|
||||
Tier tier;
|
||||
// If the wizard has no element, it picks a random one each time.
|
||||
@@ -764,17 +791,19 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
|
||||
// Uses its own special weighting
|
||||
if(randomiser < 10){
|
||||
tier = Tier.BASIC;
|
||||
tier = Tier.NOVICE;
|
||||
}else if(randomiser < 16){
|
||||
tier = Tier.APPRENTICE;
|
||||
}else{
|
||||
}else if(randomiser < 19 || !master){
|
||||
tier = Tier.ADVANCED;
|
||||
}else{
|
||||
tier = Tier.MASTER;
|
||||
}
|
||||
|
||||
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));
|
||||
List<Spell> list = Spell.getSpells(new Spell.TierElementFilter(tier, element, SpellProperties.Context.NPCS));
|
||||
// Keeps only spells which can be cast by NPCs
|
||||
list.retainAll(npcSpells);
|
||||
// Removes spells that the wizard already has
|
||||
@@ -828,12 +857,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
|
||||
this.towerBlocks = blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the block at the given coordinates is part of this wizard's tower.
|
||||
*
|
||||
* @param pos
|
||||
* @return
|
||||
*/
|
||||
/** Tests whether the block at the given coordinates is part of this wizard's tower. */
|
||||
public boolean isBlockPartOfTower(BlockPos pos){
|
||||
if(this.towerBlocks == null) return false;
|
||||
// Uses .equals() rather than == so this will work fine.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
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;
|
||||
@@ -12,6 +9,7 @@ import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.monster.EntityZombie;
|
||||
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;
|
||||
@@ -19,22 +17,22 @@ 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;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityZombieMinion extends EntityZombie implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private int lifetime = -1;
|
||||
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; }
|
||||
@Override public UUID getOwnerId(){ return casterUUID; }
|
||||
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
|
||||
|
||||
/** Creates a new zombie minion in the given world. */
|
||||
public EntityZombieMinion(World world){
|
||||
@@ -42,13 +40,13 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
// EntityZombie overrides (EntityZombie is a long class so there are lots of these)
|
||||
// EntityZombie overrides (EntityZombie is a complex 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,
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
@@ -57,6 +55,7 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
|
||||
@Override protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty){} // They don't have equipment!
|
||||
@Override public void onKillEntity(EntityLivingBase entityLivingIn){} // Turns villagers to zombies in EntityZombie
|
||||
@Override public void setChildSize(boolean isChild){}
|
||||
@Override protected ItemStack getSkullDrop(){ return ItemStack.EMPTY; }
|
||||
|
||||
// Implementations
|
||||
|
||||
@@ -121,8 +120,16 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
|
||||
@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 protected boolean canDespawn(){
|
||||
return getCaster() == null && getOwnerId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCanSpawnHere(){
|
||||
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
@@ -143,7 +150,7 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
|
||||
@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;
|
||||
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* [NYI] Interface for entities that can select between spells based on their current circumstances, to be used in
|
||||
* conjunction with {@link EntityAISelectSpell}.
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
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;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* <p></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>
|
||||
* <p></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.
|
||||
*/
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
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.data.WizardData;
|
||||
import electroblob.wizardry.integration.DamageSafetyChecker;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.util.IElementalDamage;
|
||||
import electroblob.wizardry.util.IndirectMinionDamage;
|
||||
import electroblob.wizardry.item.ISpellCastingItem;
|
||||
import electroblob.wizardry.util.*;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.MinionDamage;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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.*;
|
||||
import net.minecraft.entity.monster.IMob;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
@@ -36,28 +23,33 @@ import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 2.1, 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>
|
||||
* <p></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>
|
||||
* <p></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>
|
||||
* <p></p>
|
||||
* All damage dealt by ISummonedCreature instances is redirected via
|
||||
* {@link ISummonedCreature#onLivingAttackEvent(net.minecraftforge.event.entity.living.LivingAttackEvent)
|
||||
* {@link ISummonedCreature#onLivingAttackEvent(LivingAttackEvent)
|
||||
* ISummonedCreature.onLivingAttackEvent(LivingAttackEvent)} and replaced by an instance of
|
||||
* {@link electroblob.wizardry.util.IElementalDamage IElementalDamage} with the summoner of that creature as the source
|
||||
* {@link 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 kills for their owner,
|
||||
* dropping xp and rare loot if that owner is a player.
|
||||
* <p>
|
||||
* <p></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
|
||||
@@ -67,12 +59,12 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
* 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 (except for methods where the result of the delegate method should itself be returned).</i>
|
||||
* <p>
|
||||
* <p></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>
|
||||
* <p></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>
|
||||
*
|
||||
@@ -83,7 +75,7 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
* 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! */
|
||||
@Mod.EventBusSubscriber
|
||||
public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOwnable {
|
||||
|
||||
// Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that.
|
||||
String NAMEPLATE_TRANSLATION_KEY = "entity." + Wizardry.MODID + ":summonedcreature.nameplate";
|
||||
@@ -101,43 +93,51 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
*/
|
||||
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);
|
||||
/** Internal, do not use. Implementing classes should implement this to set their owner UUID field. */
|
||||
void setOwnerId(UUID uuid);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/** Returns the UUID of the owner of this summoned creature, or null if it does not have an owner.
|
||||
* Implementing classes should implement this to return their owner UUID field. */
|
||||
@Nullable
|
||||
WeakReference<EntityLivingBase> getCasterReference();
|
||||
@Override
|
||||
UUID getOwnerId(); // Only overridden because I wanted to add javadoc!
|
||||
|
||||
/** 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();
|
||||
@Nullable
|
||||
@Override
|
||||
default Entity getOwner(){
|
||||
return getCaster(); // Delegate to getCaster
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* another dimension, or this creature simply had no caster in the first place.
|
||||
*/
|
||||
@Nullable
|
||||
default EntityLivingBase getCaster(){
|
||||
return getCasterReference() == null ? null : getCasterReference().get();
|
||||
default EntityLivingBase getCaster(){ // Kept despite the above method because it returns an EntityLivingBase
|
||||
|
||||
if(this instanceof Entity){ // Bit of a cheat but it saves having yet another method just to get the world
|
||||
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(((Entity)this).world, getOwnerId());
|
||||
|
||||
if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen
|
||||
Wizardry.logger.warn("{} has a non-living owner!", this);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (EntityLivingBase)entity;
|
||||
|
||||
}else{
|
||||
Wizardry.logger.warn("{} implements ISummonedCreature but is not an SoundLoopSpellEntity!", this.getClass());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the EntityLivingBase that summoned this creature. <i>This is the correct method to use to set the owner of
|
||||
* this summoned creature.
|
||||
* Sets the EntityLivingBase that summoned this creature.
|
||||
*/
|
||||
default void setCaster(@Nullable EntityLivingBase caster){
|
||||
setCasterReference(new WeakReference<EntityLivingBase>(caster));
|
||||
setOwnerId(caster == null ? null : caster.getUniqueID());
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
@@ -164,17 +164,56 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
default void readSpawnData(ByteBuf buffer){
|
||||
int id = buffer.readInt();
|
||||
// We're on the client side here, so we can safely use Minecraft.getMinecraft().world via proxies.
|
||||
if(id > -1) setCasterReference(
|
||||
new WeakReference<EntityLivingBase>((EntityLivingBase)Wizardry.proxy.getTheWorld().getEntityByID(id)));
|
||||
if(id > -1){
|
||||
Entity entity = Wizardry.proxy.getTheWorld().getEntityByID(id);
|
||||
if(entity instanceof EntityLivingBase) setCaster((EntityLivingBase)entity);
|
||||
else Wizardry.logger.warn("Received a spawn packet for entity {}, but no living entity matched the supplied ID", this);
|
||||
}
|
||||
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.
|
||||
* Determines whether the given target is valid. Used by the default target selector (see
|
||||
* {@link ISummonedCreature#getTargetSelector()}) and revenge targeting checks. This method is responsible for the
|
||||
* ally designation system, default classes that may be targeted and the config whitelist/blacklist.
|
||||
* Implementors may override this if they want to do something different or add their own checks.
|
||||
* @see AllyDesignationSystem#isValidTarget(Entity, Entity)
|
||||
*/
|
||||
default boolean isValidTarget(Entity target){
|
||||
return WizardryUtilities.isValidTarget(this.getCaster(), target);
|
||||
// If the target is valid based on the ADS...
|
||||
if(AllyDesignationSystem.isValidTarget(this.getCaster(), target)){
|
||||
|
||||
// ...and is a player, they can be attacked, since players can't be in the whitelist or the
|
||||
// blacklist...
|
||||
if(target instanceof EntityPlayer){
|
||||
// ...unless the creature was summoned by a good wizard who the player has not angered.
|
||||
if(getCaster() instanceof EntityWizard){
|
||||
if(getCaster().getRevengeTarget() != target
|
||||
&& ((EntityWizard)getCaster()).getAttackTarget() != target) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ...and is a mob, a summoned creature, a wizard...
|
||||
if((target instanceof IMob || target instanceof ISummonedCreature
|
||||
|| (target instanceof EntityWizard && !(getCaster() instanceof EntityWizard))
|
||||
// ...or something that's attacking the owner...
|
||||
|| (target instanceof EntityLiving && ((EntityLiving)target).getAttackTarget() == getCaster())
|
||||
// ...or in the whitelist...
|
||||
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist)
|
||||
.contains(EntityList.getKey(target.getClass())))
|
||||
// ...and isn't in the blacklist...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
|
||||
.contains(EntityList.getKey(target.getClass()))){
|
||||
// ...it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,45 +221,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
* 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){
|
||||
// ... unless the creature was summoned by a good wizard who the player has not angered.
|
||||
if(getCaster() instanceof EntityWizard){
|
||||
if(((EntityWizard)getCaster()).getRevengeTarget() != entity
|
||||
&& ((EntityWizard)getCaster()).getAttackTarget() != entity) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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.getKey(entity.getClass())))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
|
||||
.contains(EntityList.getKey(entity.getClass()))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
return entity -> getCaster() == null ? entity instanceof EntityPlayer : !entity.isInvisible() && isValidTarget(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +242,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
* 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>
|
||||
* <p></p>
|
||||
* Usage examples: {@link EntitySilverfishMinion} 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.
|
||||
*/
|
||||
@@ -266,7 +267,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
* 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.setOwnerId(tagcompound.getUniqueId("casterUUID"));
|
||||
this.setLifetime(tagcompound.getInteger("lifetime"));
|
||||
}
|
||||
|
||||
@@ -275,8 +276,8 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
* 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;
|
||||
// Allows the config to prevent minions from revenge-targeting their owners (or anything else, for that matter)
|
||||
return Wizardry.settings.minionRevengeTargeting || isValidTarget(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,22 +287,17 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
default void updateDelegate(){
|
||||
|
||||
if(!(this instanceof Entity))
|
||||
throw new ClassCastException("Implementations of ISummonedCreature must extend Entity!");
|
||||
throw new ClassCastException("Implementations of ISummonedCreature must extend SoundLoopSpellEntity!");
|
||||
|
||||
Entity thisEntity = ((Entity)this);
|
||||
|
||||
if(this.getCaster() == null && this.getCasterUUID() != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(thisEntity.world, 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){
|
||||
// For some reason Minecraft reads the entity from NBT just after the entity is created, so setting -1 as a
|
||||
// default lifetime doesn't work. The easiest way around this is to use 0 - nobody's going to need it!
|
||||
if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() > 0){
|
||||
this.onDespawn();
|
||||
thisEntity.setDead();
|
||||
}
|
||||
@@ -322,20 +318,20 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
|
||||
ItemStack stack = player.getHeldItem(hand);
|
||||
|
||||
WizardData properties = WizardData.get(player);
|
||||
WizardData data = WizardData.get(player);
|
||||
// Selects one of the player's minions.
|
||||
if(player.isSneaking() && stack.getItem() instanceof ItemWand){
|
||||
if(player.isSneaking() && stack.getItem() instanceof ISpellCastingItem){
|
||||
|
||||
if(!player.world.isRemote && properties != null && this.getCaster() == player){
|
||||
if(!player.world.isRemote && data != null && this.getCaster() == player){
|
||||
|
||||
if(properties.selectedMinion != null && properties.selectedMinion.get() == this){
|
||||
if(data.selectedMinion != null && data.selectedMinion.get() == this){
|
||||
// Deselects the selected minion if right-clicked again
|
||||
properties.selectedMinion = null;
|
||||
data.selectedMinion = null;
|
||||
}else{
|
||||
// Selects this minion
|
||||
properties.selectedMinion = new WeakReference<ISummonedCreature>(this);
|
||||
data.selectedMinion = new WeakReference<>(this);
|
||||
}
|
||||
properties.sync();
|
||||
data.sync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -346,7 +342,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
// Damage system
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLivingAttackEvent(LivingAttackEvent event){
|
||||
static void onLivingAttackEvent(LivingAttackEvent event){
|
||||
|
||||
// Rather than bother overriding entire attack methods in ISummonedCreature implementations, it's easier (and
|
||||
// more robust) to use LivingAttackEvent to modify the damage source.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -7,7 +10,6 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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;
|
||||
@@ -24,16 +26,19 @@ public class EntityDarknessOrb extends EntityMagicProjectile {
|
||||
Entity target = rayTrace.entityHit;
|
||||
|
||||
if(target != null && !MagicDamage.isEntityImmune(DamageType.WITHER, target)){
|
||||
float damage = 8 * damageMultiplier;
|
||||
|
||||
float damage = Spells.darkness_orb.getProperty(Spell.DAMAGE).floatValue() * 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));
|
||||
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER,
|
||||
Spells.darkness_orb.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.darkness_orb.getProperty(Spell.EFFECT_STRENGTH).intValue()));
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_WITHER_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
this.playSound(WizardrySounds.ENTITY_DARKNESS_ORB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
this.setDead();
|
||||
@@ -53,10 +58,6 @@ public class EntityDarknessOrb extends EntityMagicProjectile {
|
||||
ParticleBuilder.create(Type.DARK_MAGIC, this).clr(0.1f, 0.0f, 0.0f).spawn(world);
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 150){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
// Cancels out the slowdown effect in EntityThrowable
|
||||
this.motionX /= 0.99;
|
||||
this.motionY /= 0.99;
|
||||
@@ -67,4 +68,9 @@ public class EntityDarknessOrb extends EntityMagicProjectile {
|
||||
public boolean hasNoGravity(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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 EntityDart extends EntityMagicArrow {
|
||||
@@ -15,7 +18,7 @@ public class EntityDart extends EntityMagicArrow {
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override public double getDamage(){ return 4; }
|
||||
@Override public double getDamage(){ return Spells.dart.getProperty(Spell.DAMAGE).doubleValue(); }
|
||||
|
||||
@Override public boolean doGravity(){ return true; }
|
||||
|
||||
@@ -24,13 +27,14 @@ public class EntityDart extends EntityMagicArrow {
|
||||
@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));
|
||||
entityHit.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, Spells.dart.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.dart.getProperty(Spell.EFFECT_STRENGTH).intValue(), false, false));
|
||||
this.playSound(WizardrySounds.ENTITY_DART_HIT, 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));
|
||||
public void onBlockHit(RayTraceResult hit){
|
||||
this.playSound(WizardrySounds.ENTITY_DART_HIT_BLOCK, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,4 +55,8 @@ public class EntityDart extends EntityMagicArrow {
|
||||
@Override
|
||||
protected void entityInit(){}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Disintegration;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityEmber extends EntityMagicProjectile {
|
||||
|
||||
private int extraLifetime;
|
||||
|
||||
public EntityEmber(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityEmber(World world, EntityLivingBase caster){
|
||||
super(world);
|
||||
this.thrower = caster;
|
||||
extraLifetime = rand.nextInt(30);
|
||||
this.setSize(0.1f, 0.1f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AxisAlignedBB getCollisionBoundingBox(){
|
||||
return null;//this.getEntityBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return Spells.disintegration.getProperty(Disintegration.EMBER_LIFETIME).intValue() + extraLifetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult result){
|
||||
|
||||
if(result.entityHit != null){
|
||||
result.entityHit.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue());
|
||||
}
|
||||
|
||||
if(result.typeOfHit == RayTraceResult.Type.BLOCK){
|
||||
this.inGround = true;
|
||||
this.collided = true;
|
||||
if(result.sideHit.getAxis() == EnumFacing.Axis.X) motionX = 0;
|
||||
if(result.sideHit.getAxis() == EnumFacing.Axis.Y){
|
||||
motionY = 0;
|
||||
this.collidedVertically = true;
|
||||
}
|
||||
if(result.sideHit.getAxis() == EnumFacing.Axis.Z) motionZ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyEntityCollision(Entity entity){
|
||||
|
||||
super.applyEntityCollision(entity);
|
||||
|
||||
if(entity instanceof EntityLivingBase){
|
||||
entity.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(this.collidedVertically){
|
||||
this.motionY += this.getGravityVelocity();
|
||||
this.motionX *= 0.5;
|
||||
this.motionZ *= 0.5;
|
||||
}
|
||||
|
||||
world.getEntitiesInAABBexcluding(thrower, this.getEntityBoundingBox(), e -> e instanceof EntityLivingBase)
|
||||
.forEach(e -> e.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue()));
|
||||
|
||||
// Copied from ParticleLava
|
||||
if(this.rand.nextFloat() > (float)this.ticksExisted / this.getLifetime()){
|
||||
this.world.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, this.posX, this.posY, this.posZ, this.motionX, this.motionY, this.motionZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
@@ -16,19 +19,22 @@ public class EntityFirebolt extends EntityMagicProjectile {
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
Entity entityHit = rayTrace.entityHit;
|
||||
|
||||
if(entityHit != null){
|
||||
float damage = 5 * damageMultiplier;
|
||||
|
||||
float damage = Spells.firebolt.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(),
|
||||
damage);
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) entityHit.setFire(5);
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit))
|
||||
entityHit.setFire(Spells.firebolt.getProperty(Spell.BURN_DURATION).intValue());
|
||||
}
|
||||
|
||||
this.playSound(SoundEvents.BLOCK_LAVA_POP, 2, 0.8f + rand.nextFloat() * 0.3f);
|
||||
this.playSound(WizardrySounds.ENTITY_FIREBOLT_HIT, 2, 0.8f + rand.nextFloat() * 0.3f);
|
||||
|
||||
// Particle effect
|
||||
if(world.isRemote){
|
||||
@@ -47,16 +53,20 @@ public class EntityFirebolt extends EntityMagicProjectile {
|
||||
super.onUpdate();
|
||||
|
||||
if(world.isRemote){
|
||||
for(int i = 0; i < 4; i++){
|
||||
world.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);
|
||||
ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE, this).time(14).spawn(world);
|
||||
|
||||
if(this.ticksExisted > 1){ // Don't spawn particles behind where it started!
|
||||
double x = posX - motionX/2 + rand.nextFloat() * 0.2 - 0.1;
|
||||
double y = posY + this.height/2 - motionY/2 + rand.nextFloat() * 0.2 - 0.1;
|
||||
double z = posZ - motionZ/2 + rand.nextFloat() * 0.2 - 0.1;
|
||||
ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE).pos(x, y, z).time(14).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 8){
|
||||
this.setDead();
|
||||
}
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -9,17 +10,23 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityFirebomb extends EntityBomb {
|
||||
|
||||
public EntityFirebomb(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
@@ -27,13 +34,14 @@ public class EntityFirebomb extends EntityBomb {
|
||||
|
||||
if(entityHit != null){
|
||||
// This is if the firebomb gets a direct hit
|
||||
float damage = 5 * damageMultiplier;
|
||||
float damage = Spells.firebomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(),
|
||||
damage);
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) entityHit.setFire(10);
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit))
|
||||
entityHit.setFire(Spells.firebomb.getProperty(Spell.BURN_DURATION).intValue());
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
@@ -45,7 +53,7 @@ public class EntityFirebomb extends EntityBomb {
|
||||
for(int i = 0; i < 60 * blastMultiplier; i++){
|
||||
|
||||
ParticleBuilder.create(Type.MAGIC_FIRE, rand, posX, posY, posZ, 2*blastMultiplier, false)
|
||||
.time(15 + rand.nextInt(5)).scale(2 + rand.nextFloat()).spawn(world);
|
||||
.time(10 + rand.nextInt(4)).scale(2 + rand.nextFloat()).spawn(world);
|
||||
|
||||
ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false)
|
||||
.clr(1.0f, 0.2f + rand.nextFloat() * 0.4f, 0.0f).spawn(world);
|
||||
@@ -56,10 +64,10 @@ public class EntityFirebomb extends EntityBomb {
|
||||
|
||||
if(!this.world.isRemote){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
|
||||
this.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
|
||||
this.playSound(WizardrySounds.ENTITY_FIREBOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
|
||||
this.playSound(WizardrySounds.ENTITY_FIREBOMB_FIRE, 1, 1);
|
||||
|
||||
double range = 3.0d * blastMultiplier;
|
||||
double range = Spells.firebomb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
@@ -70,8 +78,8 @@ public class EntityFirebomb extends EntityBomb {
|
||||
// Splash damage does not count as projectile damage
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE),
|
||||
4.0f * damageMultiplier);
|
||||
target.setFire(7);
|
||||
Spells.firebomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier);
|
||||
target.setFire(Spells.firebomb.getProperty(Spell.BURN_DURATION).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,100 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.item.IManaStoringItem;
|
||||
import electroblob.wizardry.item.ISpellCastingItem;
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EntityForceArrow extends EntityMagicArrow {
|
||||
|
||||
/** The mana used to cast this force arrow, used for artefacts. */
|
||||
private int mana = 0;
|
||||
|
||||
/** Creates a new force arrow in the given world. */
|
||||
public EntityForceArrow(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public void setMana(int mana){
|
||||
this.mana = mana;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.0F, 1.0F);
|
||||
this.playSound(WizardrySounds.ENTITY_FORCE_ARROW_HIT, 1.0F, 1.0F);
|
||||
if(this.world.isRemote)
|
||||
ParticleBuilder.create(Type.FLASH).pos(posX, posY, posZ).scale(1.3f).clr(0.75f, 1, 0.85f).spawn(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInGround(){
|
||||
returnManaToCaster();
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(){
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.0F, 1.0F);
|
||||
public void onUpdate(){
|
||||
|
||||
if(getLifetime() >=0 && this.ticksExisted > getLifetime()){ // The last tick before it disappears
|
||||
returnManaToCaster();
|
||||
}
|
||||
|
||||
super.onUpdate();
|
||||
}
|
||||
|
||||
private void returnManaToCaster(){
|
||||
|
||||
if(mana > 0 && getCaster() instanceof EntityPlayer){
|
||||
|
||||
EntityPlayer player = (EntityPlayer)getCaster();
|
||||
|
||||
if(!player.capabilities.isCreativeMode && ItemArtefact.isArtefactActive(player, WizardryItems.ring_mana_return)){
|
||||
|
||||
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
|
||||
if(stack.getItem() instanceof ISpellCastingItem && stack.getItem() instanceof IManaStoringItem
|
||||
&& Arrays.asList(((ISpellCastingItem)stack.getItem()).getSpells(stack)).contains(Spells.force_arrow)){
|
||||
((IManaStoringItem)stack.getItem()).rechargeMana(stack, mana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(RayTraceResult hit){
|
||||
this.playSound(WizardrySounds.ENTITY_FORCE_ARROW_HIT, 1.0F, 1.0F);
|
||||
if(this.world.isRemote){
|
||||
// Gets a position slightly away from the block hit so the particle doesn't get cut in half by the block face
|
||||
Vec3d vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(0.15));
|
||||
ParticleBuilder.create(Type.FLASH).pos(vec).scale(1.3f).clr(0.75f, 1, 0.85f).spawn(world);
|
||||
vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET));
|
||||
ParticleBuilder.create(Type.SCORCH).pos(vec).face(hit.sideHit).clr(0, 1, 0.5f).spawn(world);
|
||||
//vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET));
|
||||
//ParticleBuilder.create(Type.SCORCH).pos(vec).face(hit.sideHit).clr(0, 1, 0.5f).spawn(world);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
if(this.ticksExisted > 20){
|
||||
this.setDead();
|
||||
}
|
||||
public int getLifetime(){
|
||||
return 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDamage(){
|
||||
return 7.0d;
|
||||
return Spells.force_arrow.getProperty(Spell.DAMAGE).floatValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityForceOrb extends EntityBomb {
|
||||
|
||||
public EntityForceOrb(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
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));
|
||||
this.playSound(WizardrySounds.ENTITY_FORCE_ORB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
@@ -41,10 +48,10 @@ public class EntityForceOrb extends EntityBomb {
|
||||
|
||||
// 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);
|
||||
this.playSound(WizardrySounds.ENTITY_FORCE_ORB_HIT_BLOCK, 1.5F, pitch);
|
||||
this.playSound(WizardrySounds.ENTITY_FORCE_ORB_HIT_BLOCK, 1.5F, pitch - 0.01f);
|
||||
|
||||
double blastRadius = 4.0d * blastMultiplier;
|
||||
double blastRadius = Spells.force_orb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(blastRadius, this.posX,
|
||||
this.posY, this.posZ, this.world);
|
||||
@@ -59,7 +66,7 @@ public class EntityForceOrb extends EntityBomb {
|
||||
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;
|
||||
float damage = Spells.force_orb.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.BLAST), damage);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -12,43 +12,55 @@ 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.EnumFacing;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityIceCharge extends EntityBomb {
|
||||
|
||||
public static final String ICE_SHARDS = "ice_shards";
|
||||
|
||||
public EntityIceCharge(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult par1RayTraceResult){
|
||||
Entity entityHit = par1RayTraceResult.entityHit;
|
||||
public int getLifetime(){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
Entity entityHit = rayTrace.entityHit;
|
||||
|
||||
if(entityHit != null){
|
||||
// This is if the ice charge gets a direct hit
|
||||
float damage = 4 * damageMultiplier;
|
||||
float damage = Spells.ice_charge.getProperty(Spell.DAMAGE).floatValue() * 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));
|
||||
((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(WizardryPotions.frost,
|
||||
Spells.ice_charge.getProperty(Spell.DIRECT_EFFECT_DURATION).intValue(),
|
||||
Spells.ice_charge.getProperty(Spell.DIRECT_EFFECT_STRENGTH).intValue()));
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
if(world.isRemote){
|
||||
this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0);
|
||||
for(int i = 0; i < 30 * blastMultiplier; i++){
|
||||
|
||||
|
||||
ParticleBuilder.create(Type.ICE, rand, this.posX, this.posY, this.posZ, 2 * blastMultiplier, false)
|
||||
.time(35).gravity(true).spawn(world);
|
||||
|
||||
|
||||
float brightness = 0.4f + rand.nextFloat() * 0.5f;
|
||||
ParticleBuilder.create(Type.DARK_MAGIC, rand, this.posX, this.posY, this.posZ, 2 * blastMultiplier, false)
|
||||
.clr(brightness, brightness + 0.1f, 1.0f).spawn(world);
|
||||
@@ -57,10 +69,10 @@ public class EntityIceCharge extends EntityBomb {
|
||||
|
||||
if(!this.world.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);
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_CHARGE_SMASH, 1.5f, rand.nextFloat() * 0.4f + 0.6f);
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_CHARGE_ICE, 1.2f, rand.nextFloat() * 0.4f + 1.2f);
|
||||
|
||||
double radius = 3.0d * blastMultiplier;
|
||||
double radius = Spells.ice_charge.getProperty(Spell.EFFECT_RADIUS).floatValue() * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
@@ -69,7 +81,9 @@ public class EntityIceCharge extends EntityBomb {
|
||||
for(EntityLivingBase target : targets){
|
||||
if(target != entityHit && target != this.getThrower()){
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, target))
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost,
|
||||
Spells.ice_charge.getProperty(Spell.SPLASH_EFFECT_DURATION).intValue(),
|
||||
Spells.ice_charge.getProperty(Spell.SPLASH_EFFECT_STRENGTH).intValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,35 +93,39 @@ public class EntityIceCharge extends EntityBomb {
|
||||
|
||||
BlockPos pos = new BlockPos(this.posX + i, this.posY, this.posZ + j);
|
||||
|
||||
int y = WizardryUtilities.getNearestFloorLevelB(world, pos, 7);
|
||||
Integer y = WizardryUtilities.getNearestSurface(world, pos, EnumFacing.UP, 7, true,
|
||||
WizardryUtilities.SurfaceCriteria.SOLID_LIQUID_TO_AIR);
|
||||
|
||||
pos = new BlockPos(pos.getX(), y, pos.getZ());
|
||||
if(y != null){
|
||||
|
||||
double dist = this.getDistance(pos.getX(), pos.getY(), pos.getZ());
|
||||
pos = new BlockPos(pos.getX(), y, 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(world.getBlockState(pos.down()).getBlock() == Blocks.WATER){
|
||||
world.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.
|
||||
world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState());
|
||||
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(rand.nextInt((int)dist * 2 + 1) < 1 && dist < 2){
|
||||
if(world.getBlockState(pos.down()).getBlock() == Blocks.WATER){
|
||||
world.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.
|
||||
world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Releases shards
|
||||
for(int i = 0; i < 10; i++){
|
||||
for(int i = 0; i < Spells.ice_charge.getProperty(ICE_SHARDS).intValue(); i++){
|
||||
double dx = rand.nextDouble() - 0.5;
|
||||
double dy = rand.nextDouble() - 0.5;
|
||||
double dz = rand.nextDouble() - 0.5;
|
||||
EntityIceShard iceshard = new EntityIceShard(world);
|
||||
iceshard.setPosition(this.posX + dx, this.posY + dy, this.posZ + dz);
|
||||
iceshard.motionX = dx;
|
||||
iceshard.motionY = dy;
|
||||
iceshard.motionZ = dz;
|
||||
iceshard.motionX = dx * 1.5;
|
||||
iceshard.motionY = dy * 1.5;
|
||||
iceshard.motionZ = dz * 1.5;
|
||||
iceshard.setCaster(this.getThrower());
|
||||
iceshard.damageMultiplier = this.damageMultiplier;
|
||||
world.spawnEntity(iceshard);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceLance extends EntityMagicArrow {
|
||||
@@ -18,7 +21,9 @@ public class EntityIceLance extends EntityMagicArrow {
|
||||
this.setKnockbackStrength(1);
|
||||
}
|
||||
|
||||
@Override public double getDamage(){ return 10.0d; }
|
||||
@Override public double getDamage(){ return Spells.ice_lance.getProperty(Spell.DAMAGE).floatValue(); }
|
||||
|
||||
@Override public int getLifetime(){ return -1; }
|
||||
|
||||
@Override public DamageType getDamageType(){ return DamageType.FROST; }
|
||||
|
||||
@@ -35,13 +40,15 @@ public class EntityIceLance extends EntityMagicArrow {
|
||||
|
||||
// Adds a freeze effect to the target.
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
|
||||
entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 0));
|
||||
entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost,
|
||||
Spells.ice_lance.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.ice_lance.getProperty(Spell.EFFECT_STRENGTH).intValue()));
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_LANCE_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(){
|
||||
public void onBlockHit(RayTraceResult hit){
|
||||
// Adds a particle effect when the ice lance hits a block.
|
||||
if(this.world.isRemote){
|
||||
for(int j = 0; j < 10; j++){
|
||||
@@ -49,8 +56,8 @@ public class EntityIceLance extends EntityMagicArrow {
|
||||
.time(20 + rand.nextInt(10)).gravity(true).spawn(world);
|
||||
}
|
||||
}
|
||||
// Parameters for sound: sound event name, volume, pitch.
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.0F, rand.nextFloat() * 0.4F + 1.2F);
|
||||
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_LANCE_SMASH, 1.0F, rand.nextFloat() * 0.4F + 1.2F);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceShard extends EntityMagicArrow {
|
||||
@@ -17,7 +21,9 @@ public class EntityIceShard extends EntityMagicArrow {
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override public double getDamage(){ return 6; }
|
||||
@Override public double getDamage(){ return Spells.ice_shard.getProperty(Spell.DAMAGE).floatValue(); }
|
||||
|
||||
@Override public int getLifetime(){ return -1; }
|
||||
|
||||
@Override public DamageType getDamageType(){ return DamageType.FROST; }
|
||||
|
||||
@@ -32,13 +38,16 @@ public class EntityIceShard extends EntityMagicArrow {
|
||||
|
||||
// Adds a freeze effect to the target.
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit))
|
||||
entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 0));
|
||||
entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost,
|
||||
Spells.ice_shard.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.ice_shard.getProperty(Spell.EFFECT_STRENGTH).intValue()));
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_SHARD_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockHit(){
|
||||
public void onBlockHit(RayTraceResult hit){
|
||||
|
||||
// Adds a particle effect when the ice shard hits a block.
|
||||
if(this.world.isRemote){
|
||||
// Gets a position slightly away from the block hit so the particle doesn't get cut in half by the block face
|
||||
@@ -51,7 +60,7 @@ public class EntityIceShard extends EntityMagicArrow {
|
||||
}
|
||||
}
|
||||
// Parameters for sound: sound event name, volume, pitch.
|
||||
this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.0F, rand.nextFloat() * 0.4F + 1.2F);
|
||||
this.playSound(WizardrySounds.ENTITY_ICE_SHARD_SMASH, 1.0F, rand.nextFloat() * 0.4F + 1.2F);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceball extends EntityMagicProjectile {
|
||||
|
||||
public EntityIceball(World world){
|
||||
super(world);
|
||||
this.setSize(0.5f, 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
if(!world.isRemote){
|
||||
|
||||
Entity entityHit = rayTrace.entityHit;
|
||||
|
||||
if(entityHit != null){
|
||||
|
||||
float damage = Spells.iceball.getProperty(Spell.DAMAGE).floatValue() * 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,
|
||||
Spells.iceball.getProperty(Spell.EFFECT_DURATION).intValue(),
|
||||
Spells.iceball.getProperty(Spell.EFFECT_STRENGTH).intValue()));
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
boolean flag = true;
|
||||
|
||||
if(this.getThrower() != null && this.getThrower() instanceof EntityLiving){
|
||||
flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.getThrower());
|
||||
}
|
||||
|
||||
if(flag){
|
||||
|
||||
BlockPos pos = rayTrace.getBlockPos();
|
||||
|
||||
if(rayTrace.sideHit == EnumFacing.UP && !world.isRemote && world.isSideSolid(pos, EnumFacing.UP)
|
||||
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())){
|
||||
world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.ENTITY_ICEBALL_HIT, 2, 0.8f + rand.nextFloat() * 0.3f);
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(world.isRemote){
|
||||
|
||||
for(int i=0; i<5; i++){
|
||||
|
||||
double dx = (rand.nextDouble() - 0.5) * width;
|
||||
double dy = (rand.nextDouble() - 0.5) * height + this.height/2;
|
||||
double dz = (rand.nextDouble() - 0.5) * width;
|
||||
double v = 0.06;
|
||||
ParticleBuilder.create(ParticleBuilder.Type.SNOW)
|
||||
.pos(this.getPositionVector().add(dx - this.motionX/2, dy, dz - this.motionZ/2))
|
||||
.vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(8 + rand.nextInt(4)).spawn(world);
|
||||
|
||||
if(ticksExisted > 1){
|
||||
dx = (rand.nextDouble() - 0.5) * width;
|
||||
dy = (rand.nextDouble() - 0.5) * height + this.height / 2;
|
||||
dz = (rand.nextDouble() - 0.5) * width;
|
||||
ParticleBuilder.create(ParticleBuilder.Type.SNOW)
|
||||
.pos(this.getPositionVector().add(dx - this.motionX, dy, dz - this.motionZ))
|
||||
.vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(8 + rand.nextInt(4)).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNoGravity(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire(){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.projectile.EntityLargeFireball;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
/**
|
||||
* It's like {@link EntityMagicFireball}, but bigger... the wizardry version of vanilla's
|
||||
* {@link net.minecraft.entity.projectile.EntityLargeFireball}
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public class EntityLargeMagicFireball extends EntityMagicFireball {
|
||||
|
||||
public static final String EXPLOSION_POWER = "explosion_power";
|
||||
|
||||
/** The entity blast multiplier. This is now synced and saved centrally from {@link EntityBomb}. */
|
||||
public float blastMultiplier = 1.0f;
|
||||
|
||||
/** The explosion power of this fireball. If this is -1, the damage for the fireball
|
||||
* spell will be used instead; this is for when the fireball is not from a spell (i.e. a vanilla fireball replacement). */
|
||||
protected float explosionPower = -1;
|
||||
|
||||
public EntityLargeMagicFireball(World world){
|
||||
super(world);
|
||||
this.setSize(1, 1);
|
||||
}
|
||||
|
||||
public void setExplosionPower(float explosionPower){
|
||||
this.explosionPower = explosionPower;
|
||||
}
|
||||
|
||||
public float getExplosionPower(){
|
||||
return explosionPower == -1 ? Spells.greater_fireball.getProperty(EXPLOSION_POWER).floatValue() : explosionPower;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDamage(){
|
||||
return damage == -1 ? Spells.greater_fireball.getProperty(Spell.DAMAGE).floatValue() : damage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
if(!world.isRemote){
|
||||
boolean flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.thrower);
|
||||
this.world.newExplosion(null, this.posX, this.posY, this.posZ, getExplosionPower() * blastMultiplier, flag, flag);
|
||||
}
|
||||
|
||||
super.onImpact(rayTrace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buffer){
|
||||
buffer.writeFloat(blastMultiplier);
|
||||
super.writeSpawnData(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buffer){
|
||||
blastMultiplier = buffer.readFloat();
|
||||
super.readSpawnData(buffer);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onEntityJoinWorldEvent(EntityJoinWorldEvent event){
|
||||
// Replaces all vanilla large fireballs with wizardry ones
|
||||
if(Wizardry.settings.replaceVanillaFireballs && event.getEntity() instanceof EntityLargeFireball){
|
||||
|
||||
event.setCanceled(true);
|
||||
|
||||
EntityLargeMagicFireball fireball = new EntityLargeMagicFireball(event.getWorld());
|
||||
fireball.thrower = ((EntityLargeFireball)event.getEntity()).shootingEntity;
|
||||
fireball.setPosition(event.getEntity().posX, event.getEntity().posY, event.getEntity().posZ);
|
||||
fireball.setDamage(6);
|
||||
// Don't set the burn duration because vanilla large fireballs don't set mobs on fire directly
|
||||
fireball.setExplosionPower(((EntityLargeFireball)event.getEntity()).explosionPower);
|
||||
fireball.setLifetime(75);
|
||||
|
||||
fireball.motionX = ((EntityLargeFireball)event.getEntity()).accelerationX * ACCELERATION_CONVERSION_FACTOR;
|
||||
fireball.motionY = ((EntityLargeFireball)event.getEntity()).accelerationY * ACCELERATION_CONVERSION_FACTOR;
|
||||
fireball.motionZ = ((EntityLargeFireball)event.getEntity()).accelerationZ * ACCELERATION_CONVERSION_FACTOR;
|
||||
|
||||
event.getWorld().spawnEntity(fireball);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
@@ -14,7 +16,9 @@ public class EntityLightningArrow extends EntityMagicArrow {
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override public double getDamage(){ return 7.0d; }
|
||||
@Override public double getDamage(){ return Spells.lightning_arrow.getProperty(Spell.DAMAGE).doubleValue(); }
|
||||
|
||||
@Override public int getLifetime(){ return 20; }
|
||||
|
||||
@Override public DamageType getDamageType(){ return DamageType.SHOCK; }
|
||||
|
||||
@@ -31,26 +35,22 @@ public class EntityLightningArrow extends EntityMagicArrow {
|
||||
}
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_SPARK, 1.0F, 1.0F);
|
||||
@Override
|
||||
public void onBlockHit(RayTraceResult hit){
|
||||
if(this.world.isRemote){
|
||||
Vec3d vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET));
|
||||
ParticleBuilder.create(Type.SCORCH).pos(vec).face(hit.sideHit).clr(0.4f, 0.8f, 1).scale(0.6f).spawn(world);
|
||||
}
|
||||
this.playSound(WizardrySounds.ENTITY_LIGHTNING_ARROW_HIT, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onBlockHit(RayTraceResult hit){
|
||||
// if(this.world.isRemote){
|
||||
// Vec3d vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET));
|
||||
// ParticleBuilder.create(Type.SCORCH).pos(vec).face(hit.sideHit).clr(0.4f, 0.8f, 1).scale(0.6f).spawn(world);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
|
||||
if(this.ticksExisted > 20){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
if(world.isRemote){
|
||||
ParticleBuilder.create(Type.SPARK).pos(posX, posY, posZ).spawn(world);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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;
|
||||
|
||||
@@ -27,13 +25,12 @@ public class EntityLightningDisc extends EntityMagicProjectile {
|
||||
Entity entityHit = result.entityHit;
|
||||
|
||||
if(entityHit != null){
|
||||
float damage = 12 * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK),
|
||||
damage);
|
||||
float damage = Spells.lightning_disc.getProperty(Spell.DAMAGE).floatValue() * 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));
|
||||
this.playSound(WizardrySounds.ENTITY_LIGHTNING_DISC_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
|
||||
if(result.typeOfHit == RayTraceResult.Type.BLOCK) this.setDead();
|
||||
}
|
||||
@@ -46,47 +43,27 @@ public class EntityLightningDisc extends EntityMagicProjectile {
|
||||
// Particle effect
|
||||
if(world.isRemote){
|
||||
for(int i = 0; i < 8; i++){
|
||||
// TODO: Why are the x and z parameters different?
|
||||
ParticleBuilder.create(Type.SPARK).pos(this.posX + rand.nextFloat() * 2 - 1,
|
||||
this.posY, this.posZ + rand.nextFloat() * 2 - 1).spawn(world);
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.collided && !world.isRemote){
|
||||
|
||||
double seekingRange = 5.0d;
|
||||
|
||||
List<EntityLivingBase> entities = WizardryUtilities.getEntitiesWithinRadius(seekingRange, this.posX,
|
||||
this.posY, this.posZ, this.world);
|
||||
Entity target = null;
|
||||
|
||||
for(Entity possibleTarget : entities){
|
||||
// Decides if current entity should be replaced.
|
||||
if(target == null || this.getDistance(target) > this.getDistance(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
|
||||
public float getSeekingStrength(){
|
||||
return Spells.lightning_disc.getProperty(Spell.SEEKING_STRENGTH).floatValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNoGravity(){
|
||||
return true;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.AllyDesignationSystem;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.RayTracer;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.block.Block;
|
||||
@@ -23,22 +24,23 @@ 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.util.SoundCategory;
|
||||
import net.minecraft.util.math.*;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* <p></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).
|
||||
@@ -46,8 +48,12 @@ import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
* @since Wizardry 1.0
|
||||
* @author Electroblob
|
||||
*/
|
||||
// TODO: Might be a good idea to have this implement IEntityOwnable as well
|
||||
public abstract class EntityMagicArrow extends Entity implements IProjectile, IEntityAdditionalSpawnData {
|
||||
|
||||
public static final double LAUNCH_Y_OFFSET = 0.1;
|
||||
public static final int SEEKING_TIME = 15;
|
||||
|
||||
private int blockX = -1;
|
||||
private int blockY = -1;
|
||||
private int blockZ = -1;
|
||||
@@ -81,14 +87,14 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
|
||||
// Initialiser methods
|
||||
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projctile at the given caster's eyes and
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and
|
||||
* aims it in the direction they are looking with the given speed. */
|
||||
public void aim(EntityLivingBase caster, float speed){
|
||||
|
||||
this.setCaster(caster);
|
||||
|
||||
this.setLocationAndAngles(caster.posX, caster.posY + (double)caster.getEyeHeight(), caster.posZ,
|
||||
caster.rotationYaw, caster.rotationPitch);
|
||||
this.setLocationAndAngles(caster.posX, caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET,
|
||||
caster.posZ, caster.rotationYaw, caster.rotationPitch);
|
||||
|
||||
this.posX -= (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * 0.16F);
|
||||
this.posY -= 0.10000000149011612D;
|
||||
@@ -106,7 +112,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
this.shoot(this.motionX, this.motionY, this.motionZ, speed * 1.5F, 1.0F);
|
||||
}
|
||||
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projctile at the given caster's eyes and
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and
|
||||
* aims it at the given target with the given speed. The trajectory will 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. */
|
||||
@@ -114,7 +120,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
|
||||
this.setCaster(caster);
|
||||
|
||||
this.posY = caster.posY + (double)caster.getEyeHeight() - 0.1d;
|
||||
this.posY = caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET;
|
||||
double dx = target.posX - caster.posX;
|
||||
double dy = this.doGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - this.posY
|
||||
: target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - this.posY;
|
||||
@@ -141,6 +147,10 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
/** Subclasses must override this to set their own base damage. */
|
||||
public abstract double getDamage();
|
||||
|
||||
/** Returns the maximum flight time in ticks before this projectile disappears, or -1 if it can continue
|
||||
* indefinitely until it hits something. This should be constant. */
|
||||
public abstract int getLifetime();
|
||||
|
||||
/** Override this to specify the damage type dealt. Defaults to {@link DamageType#MAGIC}. */
|
||||
public DamageType getDamageType(){
|
||||
return DamageType.MAGIC;
|
||||
@@ -167,6 +177,16 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the seeking strength of this projectile, or the maximum distance from a target the projectile can be
|
||||
* heading for that will make it curve towards that target. By default, this is 2 if the caster is wearing a ring
|
||||
* of attraction, otherwise it is 0.
|
||||
*/
|
||||
public float getSeekingStrength(){
|
||||
return getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(),
|
||||
WizardryItems.ring_seeking) ? 2 : 0;
|
||||
}
|
||||
|
||||
// Setters and getters
|
||||
|
||||
/** Sets the amount of knockback the projectile applies when it hits a mob. */
|
||||
@@ -184,7 +204,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
}
|
||||
|
||||
public void setCaster(EntityLivingBase entity){
|
||||
caster = new WeakReference<EntityLivingBase>(entity);
|
||||
caster = new WeakReference<>(entity);
|
||||
}
|
||||
|
||||
// Methods triggered during the update cycle
|
||||
@@ -211,10 +231,15 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Projectile disappears after its lifetime (if it has one) has elapsed
|
||||
if(getLifetime() >=0 && this.ticksExisted > getLifetime()){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
if(this.getCaster() == null && this.casterUUID != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID);
|
||||
if(entity instanceof EntityLivingBase){
|
||||
this.caster = new WeakReference<EntityLivingBase>((EntityLivingBase)entity);
|
||||
this.caster = new WeakReference<>((EntityLivingBase)entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,8 +348,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
if(this.getCaster() == null){
|
||||
damagesource = DamageSource.causeThrownDamage(this, this);
|
||||
}else{
|
||||
damagesource = MagicDamage.causeIndirectMagicDamage(this,
|
||||
(EntityLivingBase)this.getCaster(), this.getDamageType()).setProjectile();
|
||||
damagesource = MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), this.getDamageType()).setProjectile();
|
||||
}
|
||||
|
||||
if(raytraceresult.entityHit.attackEntityFrom(damagesource,
|
||||
@@ -347,11 +371,9 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
}
|
||||
|
||||
// Thorns enchantment
|
||||
if(this.getCaster() != null
|
||||
&& this.getCaster() instanceof EntityLivingBase){
|
||||
if(this.getCaster() != null){
|
||||
EnchantmentHelper.applyThornEnchantments(entityHit, this.getCaster());
|
||||
EnchantmentHelper.applyArthropodEnchantments((EntityLivingBase)this.getCaster(),
|
||||
entityHit);
|
||||
EnchantmentHelper.applyArthropodEnchantments(this.getCaster(), entityHit);
|
||||
}
|
||||
|
||||
if(this.getCaster() != null && raytraceresult.entityHit != this.getCaster()
|
||||
@@ -401,6 +423,29 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
}
|
||||
}
|
||||
|
||||
// Seeking
|
||||
if(getSeekingStrength() > 0){
|
||||
|
||||
Vec3d velocity = new Vec3d(motionX, motionY, motionZ);
|
||||
|
||||
RayTraceResult hit = RayTracer.rayTrace(world, this.getPositionVector(),
|
||||
this.getPositionVector().add(velocity.scale(SEEKING_TIME)), getSeekingStrength(), false,
|
||||
true, false, EntityLivingBase.class, RayTracer.ignoreEntityFilter(null));
|
||||
|
||||
if(hit != null && hit.entityHit != null){
|
||||
|
||||
if(AllyDesignationSystem.isValidTarget(getCaster(), hit.entityHit)){
|
||||
|
||||
Vec3d direction = new Vec3d(hit.entityHit.posX, hit.entityHit.posY + hit.entityHit.height/2,
|
||||
hit.entityHit.posZ).subtract(this.getPositionVector()).normalize().scale(velocity.length());
|
||||
|
||||
motionX = motionX + 2 * (direction.x - motionX) / SEEKING_TIME;
|
||||
motionY = motionY + 2 * (direction.y - motionY) / SEEKING_TIME;
|
||||
motionZ = motionZ + 2 * (direction.z - motionZ) / SEEKING_TIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.posX += this.motionX;
|
||||
this.posY += this.motionY;
|
||||
this.posZ += this.motionZ;
|
||||
@@ -510,8 +555,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
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());
|
||||
ResourceLocation resourcelocation = Block.REGISTRY.getNameForObject(this.stuckInBlock.getBlock());
|
||||
tag.setString("inTile", resourcelocation == null ? "" : resourcelocation.toString());
|
||||
}
|
||||
tag.setByte("inData", (byte)this.inData);
|
||||
@@ -529,7 +573,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
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.
|
||||
// Commented out for now because there's some funny stuff going on with blockstates and id.
|
||||
// this.stuckInBlock = Block.getBlockById(tag.getByte("inTile") & 255);
|
||||
this.inData = tag.getByte("inData") & 255;
|
||||
this.arrowShake = tag.getByte("shake") & 255;
|
||||
@@ -545,7 +589,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buffer){
|
||||
if(buffer.isReadable()) this.caster = new WeakReference<EntityLivingBase>(
|
||||
if(buffer.isReadable()) this.caster = new WeakReference<>(
|
||||
(EntityLivingBase)this.world.getEntityByID(buffer.readInt()));
|
||||
}
|
||||
|
||||
@@ -565,6 +609,11 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE
|
||||
public float getShadowSize(){
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoundCategory getSoundCategory(){
|
||||
return WizardrySounds.SPELLS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit(){}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.projectile.EntitySmallFireball;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
/**
|
||||
* It's a fireball - but unlike vanilla fireballs, it actually looks like a fireball, and isn't completely useless for
|
||||
* attacking things (acceleration from stationary? Really, Mojang? No wonder I had so many blaze rods back in the day...)
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public class EntityMagicFireball extends EntityMagicProjectile {
|
||||
|
||||
protected static final int ACCELERATION_CONVERSION_FACTOR = 10;
|
||||
|
||||
/** The damage dealt by this fireball. If this is -1, the damage for the fireball spell will be used instead;
|
||||
* this is for when the fireball is not from a spell (i.e. a vanilla fireball replacement). */
|
||||
protected float damage = -1;
|
||||
/** The number of seconds entities are set on fire by this fireball. If this is -1, the damage for the fireball
|
||||
* spell will be used instead; this is for when the fireball is not from a spell (i.e. a vanilla fireball replacement). */
|
||||
protected int burnDuration = -1;
|
||||
/** The lifetime of this fireball in ticks. This needs to be stored so that it can be changed for vanilla replacements,
|
||||
* or mobs that shoot fireballs would have severely reduced range! */
|
||||
protected int lifetime = 16;
|
||||
|
||||
public EntityMagicFireball(World world){
|
||||
super(world);
|
||||
this.setSize(0.5f, 0.5f);
|
||||
}
|
||||
|
||||
public void setDamage(float damage){
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
public void setBurnDuration(int burnDuration){
|
||||
this.burnDuration = burnDuration;
|
||||
}
|
||||
|
||||
public float getDamage(){
|
||||
// I'm lazy, I'd rather not have an entire fireball spell class just to set two fields on the entity
|
||||
return damage == -1 ? Spells.fireball.getProperty(Spell.DAMAGE).floatValue() : damage;
|
||||
}
|
||||
|
||||
public int getBurnDuration(){
|
||||
return burnDuration == -1 ? Spells.fireball.getProperty(Spell.BURN_DURATION).intValue() : burnDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
if(!world.isRemote){
|
||||
|
||||
Entity entityHit = rayTrace.entityHit;
|
||||
|
||||
if(entityHit != null){
|
||||
|
||||
float damage = getDamage() * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(),
|
||||
damage);
|
||||
|
||||
if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit) && getBurnDuration() > 0)
|
||||
entityHit.setFire(getBurnDuration());
|
||||
|
||||
}else{
|
||||
|
||||
boolean flag = true;
|
||||
|
||||
if(this.getThrower() != null && this.getThrower() instanceof EntityLiving){
|
||||
flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.getThrower());
|
||||
}
|
||||
|
||||
if(flag){
|
||||
|
||||
BlockPos blockpos = rayTrace.getBlockPos().offset(rayTrace.sideHit);
|
||||
|
||||
if(this.world.isAirBlock(blockpos)){
|
||||
this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this.playSound(WizardrySounds.ENTITY_MAGIC_FIREBALL_HIT, 2, 0.8f + rand.nextFloat() * 0.3f);
|
||||
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(world.isRemote){
|
||||
|
||||
for(int i=0; i<5; i++){
|
||||
|
||||
double dx = (rand.nextDouble() - 0.5) * width;
|
||||
double dy = (rand.nextDouble() - 0.5) * height + this.height/2 - 0.1; // -0.1 because flames aren't centred
|
||||
double dz = (rand.nextDouble() - 0.5) * width;
|
||||
double v = 0.06;
|
||||
ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE)
|
||||
.pos(this.getPositionVector().add(dx - this.motionX/2, dy, dz - this.motionZ/2))
|
||||
.vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(10).spawn(world);
|
||||
|
||||
if(ticksExisted > 1){
|
||||
dx = (rand.nextDouble() - 0.5) * width;
|
||||
dy = (rand.nextDouble() - 0.5) * height + this.height / 2 - 0.1;
|
||||
dz = (rand.nextDouble() - 0.5) * width;
|
||||
ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE)
|
||||
.pos(this.getPositionVector().add(dx - this.motionX, dy, dz - this.motionZ))
|
||||
.vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(10).spawn(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLifetime(int lifetime){
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNoGravity(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRenderOnFire(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf buffer){
|
||||
buffer.writeInt(lifetime);
|
||||
super.writeSpawnData(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf buffer){
|
||||
lifetime = buffer.readInt();
|
||||
super.readSpawnData(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
lifetime = nbttagcompound.getInteger("lifetime");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
nbttagcompound.setInteger("lifetime", lifetime);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onEntityJoinWorldEvent(EntityJoinWorldEvent event){
|
||||
// Replaces all vanilla fireballs with wizardry ones
|
||||
if(Wizardry.settings.replaceVanillaFireballs && event.getEntity() instanceof EntitySmallFireball){
|
||||
|
||||
event.setCanceled(true);
|
||||
|
||||
EntityMagicFireball fireball = new EntityMagicFireball(event.getWorld());
|
||||
fireball.thrower = ((EntitySmallFireball)event.getEntity()).shootingEntity;
|
||||
fireball.setPosition(event.getEntity().posX, event.getEntity().posY, event.getEntity().posZ);
|
||||
fireball.setDamage(5);
|
||||
fireball.setBurnDuration(5);
|
||||
fireball.setLifetime(40);
|
||||
|
||||
fireball.motionX = ((EntitySmallFireball)event.getEntity()).accelerationX * ACCELERATION_CONVERSION_FACTOR;
|
||||
fireball.motionY = ((EntitySmallFireball)event.getEntity()).accelerationY * ACCELERATION_CONVERSION_FACTOR;
|
||||
fireball.motionZ = ((EntitySmallFireball)event.getEntity()).accelerationZ * ACCELERATION_CONVERSION_FACTOR;
|
||||
|
||||
event.getWorld().spawnEntity(fireball);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,25 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityMagicMissile extends EntityMagicArrow {
|
||||
|
||||
/** The number of ticks the magic missile flies for before vanishing; effectively determines its range. */
|
||||
private static final int LIFETIME = 12;
|
||||
|
||||
/** Creates a new magic missile in the given world. */
|
||||
public EntityMagicMissile(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override public double getDamage(){ return 4; }
|
||||
@Override public double getDamage(){ return Spells.magic_missile.getProperty(Spell.DAMAGE).floatValue(); }
|
||||
|
||||
@Override public int getLifetime(){ return 12; }
|
||||
|
||||
@Override public boolean doGravity(){ return false; }
|
||||
|
||||
@@ -26,8 +27,8 @@ public class EntityMagicMissile extends EntityMagicArrow {
|
||||
|
||||
@Override
|
||||
public void onEntityHit(EntityLivingBase entityHit){
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
if(this.world.isRemote) spawnImpactParticles();
|
||||
this.playSound(WizardrySounds.ENTITY_MAGIC_MISSILE_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
if(this.world.isRemote) ParticleBuilder.create(Type.FLASH).pos(posX, posY, posZ).clr(1, 1, 0.65f).spawn(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,10 +43,6 @@ public class EntityMagicMissile extends EntityMagicArrow {
|
||||
@Override
|
||||
public void tickInAir(){
|
||||
|
||||
if(this.ticksExisted > LIFETIME){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
if(this.world.isRemote){
|
||||
ParticleBuilder.create(Type.SPARKLE, rand, posX, posY, posZ, 0.03, true).clr(1, 1, 0.65f).fade(0.7f, 0, 1)
|
||||
.time(20 + rand.nextInt(10)).spawn(world);
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.item.ItemArtefact;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.AllyDesignationSystem;
|
||||
import electroblob.wizardry.util.RayTracer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.projectile.EntityThrowable;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
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;
|
||||
|
||||
@@ -13,7 +22,7 @@ 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>
|
||||
* <p></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(), though due to a bug in vanilla it has to be synced by this class.
|
||||
@@ -24,6 +33,9 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
*/
|
||||
public abstract class EntityMagicProjectile extends EntityThrowable implements IEntityAdditionalSpawnData {
|
||||
|
||||
public static final double LAUNCH_Y_OFFSET = 0.1;
|
||||
public static final int SEEKING_TIME = 15;
|
||||
|
||||
public float damageMultiplier = 1.0f;
|
||||
|
||||
/** Creates a new projectile in the given world. */
|
||||
@@ -33,10 +45,10 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I
|
||||
|
||||
// Initialiser methods
|
||||
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projctile at the given caster's eyes and
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and
|
||||
* aims it in the direction they are looking with the given speed. */
|
||||
public void aim(EntityLivingBase caster, float speed){
|
||||
this.setPosition(caster.posX, caster.posY + (double)caster.getEyeHeight() - 0.1d, caster.posZ);
|
||||
this.setPosition(caster.posX, caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET, caster.posZ);
|
||||
// This is the standard set of parameters for this method, used by snowballs and ender pearls amongst others.
|
||||
this.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, speed, 1.0f);
|
||||
this.thrower = caster;
|
||||
@@ -44,7 +56,7 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I
|
||||
this.ignoreEntity = caster;
|
||||
}
|
||||
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projctile at the given caster's eyes and
|
||||
/** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and
|
||||
* aims it at the given target with the given speed. The trajectory will 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. */
|
||||
@@ -54,7 +66,7 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I
|
||||
// Mojang's 'fix' for the projectile-hitting-thrower bug actually made the problem worse, hence the following line.
|
||||
this.ignoreEntity = thrower;
|
||||
|
||||
this.posY = caster.posY + (double)caster.getEyeHeight() - 0.1d;
|
||||
this.posY = caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET;
|
||||
double dx = target.posX - caster.posX;
|
||||
double dy = !this.hasNoGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - this.posY
|
||||
: target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - this.posY;
|
||||
@@ -75,6 +87,54 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I
|
||||
}
|
||||
}
|
||||
|
||||
public void setCaster(EntityLivingBase caster){
|
||||
this.thrower = caster;
|
||||
this.ignoreEntity = caster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the seeking strength of this projectile, or the maximum distance from a target the projectile can be
|
||||
* heading for that will make it curve towards that target. By default, this is 2 if the caster is wearing a ring
|
||||
* of attraction, otherwise it is 0.
|
||||
*/
|
||||
public float getSeekingStrength(){
|
||||
return getThrower() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getThrower(),
|
||||
WizardryItems.ring_seeking) ? 2 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(getLifetime() >=0 && this.ticksExisted > getLifetime()){
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
// Seeking
|
||||
if(getSeekingStrength() > 0){
|
||||
|
||||
Vec3d velocity = new Vec3d(motionX, motionY, motionZ);
|
||||
|
||||
RayTraceResult hit = RayTracer.rayTrace(world, this.getPositionVector(),
|
||||
this.getPositionVector().add(velocity.scale(SEEKING_TIME)), getSeekingStrength(), false,
|
||||
true, false, EntityLivingBase.class, RayTracer.ignoreEntityFilter(null));
|
||||
|
||||
if(hit != null && hit.entityHit != null){
|
||||
|
||||
if(AllyDesignationSystem.isValidTarget(getThrower(), hit.entityHit)){
|
||||
|
||||
Vec3d direction = new Vec3d(hit.entityHit.posX, hit.entityHit.posY + hit.entityHit.height/2,
|
||||
hit.entityHit.posZ).subtract(this.getPositionVector()).normalize().scale(velocity.length());
|
||||
|
||||
motionX = motionX + 2 * (direction.x - motionX) / SEEKING_TIME;
|
||||
motionY = motionY + 2 * (direction.y - motionY) / SEEKING_TIME;
|
||||
motionZ = motionZ + 2 * (direction.z - motionZ) / SEEKING_TIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
@@ -88,20 +148,26 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I
|
||||
}
|
||||
|
||||
@Override
|
||||
// For now, we're only writing when the thrower exists, so subclasses MUST CALL SUPER LAST.
|
||||
// TODO: Figure out whether there's a default value we can write that is never used as an entity id (0? -1? +/-MAX_VALUE?)
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
if(this.getThrower() != null) data.writeInt(this.getThrower().getEntityId());
|
||||
data.writeInt(this.getThrower() == null ? -1 : this.getThrower().getEntityId());
|
||||
}
|
||||
|
||||
@Override
|
||||
// For now, we're only writing when the thrower exists, so subclasses MUST CALL SUPER LAST.
|
||||
public void readSpawnData(ByteBuf data){
|
||||
if(data.isReadable()){
|
||||
Entity entity = this.world.getEntityByID(data.readInt());
|
||||
if(entity instanceof EntityLivingBase) this.thrower = (EntityLivingBase)entity;
|
||||
this.ignoreEntity = this.thrower;
|
||||
}
|
||||
int id = data.readInt();
|
||||
if(id == -1) return;
|
||||
Entity entity = this.world.getEntityByID(id);
|
||||
if(entity instanceof EntityLivingBase) this.thrower = (EntityLivingBase)entity;
|
||||
this.ignoreEntity = this.thrower;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoundCategory getSoundCategory(){
|
||||
return WizardrySounds.SPELLS;
|
||||
}
|
||||
|
||||
/** Returns the maximum flight time in ticks before this projectile disappears, or -1 if it can continue
|
||||
* indefinitely until it hits something. This should be constant. */
|
||||
public abstract int getLifetime();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -10,18 +11,24 @@ 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntityPoisonBomb extends EntityBomb {
|
||||
|
||||
public EntityPoisonBomb(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
@@ -29,14 +36,16 @@ public class EntityPoisonBomb extends EntityBomb {
|
||||
|
||||
if(entityHit != null){
|
||||
// This is if the poison bomb gets a direct hit
|
||||
float damage = 5 * damageMultiplier;
|
||||
float damage = Spells.poison_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * 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));
|
||||
((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(MobEffects.POISON,
|
||||
Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_DURATION).intValue(),
|
||||
Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_STRENGTH).intValue()));
|
||||
}
|
||||
|
||||
// Particle effect
|
||||
@@ -48,7 +57,7 @@ public class EntityPoisonBomb extends EntityBomb {
|
||||
for(int i = 0; i < 60 * blastMultiplier; i++){
|
||||
|
||||
ParticleBuilder.create(Type.SPARKLE, rand, posX, posY, posZ, 2*blastMultiplier, false).time(35)
|
||||
.clr(0.2f + rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world);
|
||||
.scale(2).clr(0.2f + rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world);
|
||||
|
||||
ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false)
|
||||
.clr(0.2f + rand.nextFloat() * 0.2f, 0.8f, 0.0f).spawn(world);
|
||||
@@ -60,10 +69,10 @@ public class EntityPoisonBomb extends EntityBomb {
|
||||
|
||||
if(!this.world.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);
|
||||
this.playSound(WizardrySounds.ENTITY_POISON_BOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
|
||||
this.playSound(WizardrySounds.ENTITY_POISON_BOMB_POISON, 1.2F, 1.0f);
|
||||
|
||||
double range = 3.0d * blastMultiplier;
|
||||
double range = Spells.poison_bomb.getProperty(Spell.EFFECT_RADIUS).floatValue() * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
@@ -73,8 +82,10 @@ public class EntityPoisonBomb extends EntityBomb {
|
||||
&& !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));
|
||||
Spells.poison_bomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier);
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.POISON,
|
||||
Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_DURATION).intValue(),
|
||||
Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_STRENGTH).intValue()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
@@ -10,18 +11,24 @@ 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntitySmokeBomb extends EntityBomb {
|
||||
|
||||
public EntitySmokeBomb(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
@@ -47,25 +54,26 @@ public class EntitySmokeBomb extends EntityBomb {
|
||||
|
||||
if(!this.world.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);
|
||||
this.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F);
|
||||
this.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_SMOKE, 1.2F, 1.0f);
|
||||
|
||||
double range = 3.0d * blastMultiplier;
|
||||
double range = Spells.smoke_bomb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
|
||||
int duration = Spells.smoke_bomb.getProperty(Spell.EFFECT_DURATION).intValue();
|
||||
|
||||
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));
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0));
|
||||
}else if(target instanceof EntityLiving){
|
||||
// New AI
|
||||
((EntityLiving)target).setAttackTarget(null);
|
||||
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, 120, 0));
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, duration, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
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;
|
||||
|
||||
@@ -26,13 +24,13 @@ public class EntitySpark extends EntityMagicProjectile {
|
||||
|
||||
if(entityHit != null){
|
||||
|
||||
float damage = 6 * damageMultiplier;
|
||||
entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK),
|
||||
damage);
|
||||
float damage = Spells.homing_spark.getProperty(Spell.DAMAGE).floatValue() * 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));
|
||||
this.playSound(WizardrySounds.ENTITY_HOMING_SPARK_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
|
||||
// Particle effect
|
||||
if(world.isRemote){
|
||||
@@ -47,40 +45,16 @@ public class EntitySpark extends EntityMagicProjectile {
|
||||
this.setDead();
|
||||
}
|
||||
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
if(!this.collided && !world.isRemote){
|
||||
|
||||
double seekingRange = 5.0d;
|
||||
|
||||
List<EntityLivingBase> entities = WizardryUtilities.getEntitiesWithinRadius(seekingRange, this.posX,
|
||||
this.posY, this.posZ, this.world);
|
||||
Entity target = null;
|
||||
|
||||
for(Entity possibleTarget : entities){
|
||||
// Decides if current entity should be replaced.
|
||||
if(target == null || this.getDistance(target) > this.getDistance(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();
|
||||
}
|
||||
@Override
|
||||
public float getSeekingStrength(){
|
||||
return Spells.homing_spark.getProperty(Spell.SEEKING_STRENGTH).floatValue();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNoGravity(){
|
||||
return true;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
@@ -11,28 +11,36 @@ import electroblob.wizardry.util.WizardryUtilities;
|
||||
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.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntitySparkBomb extends EntityBomb {
|
||||
|
||||
public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets";
|
||||
|
||||
public EntitySparkBomb(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImpact(RayTraceResult rayTrace){
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST_FAR, 0.5f, 0.5f);
|
||||
this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT_BLOCK, 0.5f, 0.5f);
|
||||
|
||||
Entity entityHit = rayTrace.entityHit;
|
||||
|
||||
if(entityHit != null){
|
||||
// This is if the spark bomb gets a direct hit
|
||||
float damage = 6 * damageMultiplier;
|
||||
float damage = Spells.spark_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
|
||||
|
||||
entityHit.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(),
|
||||
@@ -45,19 +53,19 @@ public class EntitySparkBomb extends EntityBomb {
|
||||
ParticleBuilder.spawnShockParticles(world, posX, posY + height/2, posZ);
|
||||
}
|
||||
|
||||
double seekerRange = 5.0d * blastMultiplier;
|
||||
double seekerRange = Spells.spark_bomb.getProperty(Spell.EFFECT_RADIUS).doubleValue() * blastMultiplier;
|
||||
|
||||
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, this.posY,
|
||||
this.posZ, this.world);
|
||||
|
||||
for(int i = 0; i < Math.min(targets.size(), 4); i++){
|
||||
for(int i = 0; i < Math.min(targets.size(), Spells.spark_bomb.getProperty(SECONDARY_MAX_TARGETS).intValue()); i++){
|
||||
|
||||
boolean flag = targets.get(i) != entityHit && targets.get(i) != this.getThrower()
|
||||
&& !(targets.get(i) instanceof EntityPlayer
|
||||
&& ((EntityPlayer)targets.get(i)).capabilities.isCreativeMode);
|
||||
&& ((EntityPlayer)targets.get(i)).isCreative());
|
||||
|
||||
// Detects (client side) if target is the thrower, to stop particles being spawned around them.
|
||||
//if(flag && world.isRemote && targets.get(i).getEntityId() == this.casterID) flag = false;
|
||||
//if(flag && world.isRemote && targets.get(i).getEntityId() == this.playerID) flag = false;
|
||||
|
||||
if(flag){
|
||||
|
||||
@@ -69,7 +77,7 @@ public class EntitySparkBomb extends EntityBomb {
|
||||
|
||||
target.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK),
|
||||
5.0f * damageMultiplier);
|
||||
Spells.spark_bomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier);
|
||||
|
||||
}else{
|
||||
ParticleBuilder.create(Type.LIGHTNING).pos(this.getPositionVector()).target(target).spawn(world);
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package electroblob.wizardry.entity.projectile;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.ParticleBuilder;
|
||||
import electroblob.wizardry.util.ParticleBuilder.Type;
|
||||
import net.minecraft.entity.Entity;
|
||||
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 static final String KNOCKBACK_STRENGTH = "knockback_strength";
|
||||
|
||||
public EntityThunderbolt(World par1World){
|
||||
super(par1World);
|
||||
}
|
||||
@@ -27,17 +31,19 @@ public class EntityThunderbolt extends EntityMagicProjectile {
|
||||
|
||||
if(entityHit != null){
|
||||
|
||||
float damage = 3 * damageMultiplier;
|
||||
float damage = Spells.thunderbolt.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier;
|
||||
|
||||
entityHit.attackEntityFrom(
|
||||
MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(),
|
||||
damage);
|
||||
|
||||
float knockbackStrength = Spells.thunderbolt.getProperty(KNOCKBACK_STRENGTH).floatValue();
|
||||
|
||||
// Knockback
|
||||
entityHit.addVelocity(this.motionX * 0.2, this.motionY * 0.2, this.motionZ * 0.2);
|
||||
entityHit.addVelocity(this.motionX * knockbackStrength, this.motionY * knockbackStrength, this.motionZ * knockbackStrength);
|
||||
}
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_LARGE_BLAST, 1.4F, 0.5f + this.rand.nextFloat() * 0.1F);
|
||||
this.playSound(WizardrySounds.ENTITY_THUNDERBOLT_HIT, 1.4F, 0.5f + this.rand.nextFloat() * 0.1F);
|
||||
|
||||
// Particle effect
|
||||
if(world.isRemote){
|
||||
@@ -60,10 +66,11 @@ public class EntityThunderbolt extends EntityMagicProjectile {
|
||||
this.posZ + rand.nextFloat() * 0.2 - 0.1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.ticksExisted > 8){
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getLifetime(){
|
||||
return 8;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user