Initial commit
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.packet.PacketNPCCastSpell;
|
||||
import electroblob.wizardry.packet.WizardryPacketHandler;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
|
||||
|
||||
/** Entity AI class for use by instances of {@link ISpellCaster}. This deals with pathing, the spell casting itself
|
||||
* and the attack cooldown. Also provides an automatic implementation of continuous spell casting using the methods
|
||||
* specified in {@code ISpellCaster}; all the entity class needs to do is implement those methods. */
|
||||
public class EntityAIAttackSpell extends EntityAIBase {
|
||||
|
||||
/** The entity the AI instance has been applied to. */
|
||||
private final EntityLiving attacker;
|
||||
/** The entity the AI instance has been applied to, but as an ISpellCaster. */
|
||||
private final ISpellCaster caster;
|
||||
/** The tagret to be attacked. */
|
||||
private EntityLivingBase target;
|
||||
/** Decremented each tick while greater than 0. When a spell is cast, this is set to that spell's cooldown plus
|
||||
* the base cooldown. */
|
||||
private int cooldown;
|
||||
/** The number of ticks between the entity finding a new target and when it first starts attacking, and also the
|
||||
* amount that is added to the spell's cooldown between casting spells. */
|
||||
private final int baseCooldown;
|
||||
/** Decremented each tick while greater than 0. When a continuous spell is first cast, this is set to
|
||||
* the value of {@link EntityAIAttackSpell#continuousSpellDuration}. */
|
||||
// I think that in this case this is only necessary on the server side. If any inconsistent behaviour
|
||||
// occurs, look into syncing this as well.
|
||||
private int continuousSpellTimer;
|
||||
/** The number of ticks that continuous spells will be cast for before cooling down. */
|
||||
private final int continuousSpellDuration;
|
||||
/** The speed that the entity should move when attacking. Only used when passed into the navigator. */
|
||||
private final double speed;
|
||||
private int seeTime;
|
||||
private final float maxAttackDistance;
|
||||
|
||||
/**
|
||||
* Creates a new spell attack AI with the given parameters.
|
||||
* @param attacker The entity that that uses this AI.
|
||||
* @param speed The speed that the entity should move when attacking. Only used when passed into the navigator.
|
||||
* @param maxDistance The maximum distance the entity should be from its target.
|
||||
* @param baseCooldown The number of ticks between the entity finding a new target and when it first starts attacking,
|
||||
* and also the amount that is added to the cooldown of the spell that has just been cast.
|
||||
* @param continuousSpellDuration The number of ticks that continuous spells will be cast for before cooling down.
|
||||
*/
|
||||
public EntityAIAttackSpell(ISpellCaster attacker, double speed, float maxDistance, int baseCooldown, int continuousSpellDuration){
|
||||
|
||||
this.cooldown = -1;
|
||||
|
||||
if(!(attacker instanceof EntityLiving)){
|
||||
throw new IllegalArgumentException("Tried to create an EntityAICastSpell for an entity that isn't an EntityLiving");
|
||||
}else{
|
||||
this.caster = attacker;
|
||||
this.attacker = (EntityLiving)attacker;
|
||||
this.baseCooldown = baseCooldown;
|
||||
this.continuousSpellDuration = continuousSpellDuration;
|
||||
this.speed = speed;
|
||||
this.maxAttackDistance = maxDistance * maxDistance;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExecute(){
|
||||
|
||||
EntityLivingBase entitylivingbase = this.attacker.getAttackTarget();
|
||||
|
||||
if(entitylivingbase == null){
|
||||
return false;
|
||||
}else{
|
||||
this.target = entitylivingbase;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueExecuting(){
|
||||
return this.shouldExecute() || !this.attacker.getNavigator().noPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetTask(){
|
||||
this.target = null;
|
||||
this.seeTime = 0;
|
||||
this.cooldown = -1;
|
||||
this.setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.continuousSpellTimer = 0;
|
||||
}
|
||||
|
||||
private void setContinuousSpellAndNotify(Spell spell, SpellModifiers modifiers){
|
||||
caster.setContinuousSpell(spell);
|
||||
WizardryPacketHandler.net.sendToAllAround(new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target == null ? -1 : target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers),
|
||||
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
|
||||
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTask(){
|
||||
|
||||
// Only executed server side.
|
||||
|
||||
double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.getEntityBoundingBox().minY, this.target.posZ);
|
||||
boolean targetIsVisible = this.attacker.getEntitySenses().canSee(this.target);
|
||||
|
||||
if(targetIsVisible){
|
||||
++this.seeTime;
|
||||
}else{
|
||||
this.seeTime = 0;
|
||||
}
|
||||
|
||||
if(distanceSq <= (double)this.maxAttackDistance && this.seeTime >= 20){
|
||||
this.attacker.getNavigator().clearPathEntity();
|
||||
}else{
|
||||
this.attacker.getNavigator().tryMoveToEntityLiving(this.target, this.speed);
|
||||
}
|
||||
|
||||
this.attacker.getLookHelper().setLookPositionWithEntity(this.target, 30.0F, 30.0F);
|
||||
|
||||
if(this.continuousSpellTimer > 0){
|
||||
|
||||
this.continuousSpellTimer--;
|
||||
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
|
||||
|| !caster.getContinuousSpell().cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND,
|
||||
this.continuousSpellDuration - this.continuousSpellTimer, target, caster.getModifiers())
|
||||
|| this.continuousSpellTimer == 0){
|
||||
// If the spell no longer succeeds, the target goes out of range or sight, or the time has elapsed,
|
||||
// reset the continuous spell timer and start the cooldown.
|
||||
this.continuousSpellTimer = 0;
|
||||
setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.cooldown = this.baseCooldown;
|
||||
return;
|
||||
}
|
||||
|
||||
}else if(--this.cooldown == 0){
|
||||
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!attacker.isPotionActive(WizardryPotions.arcane_jammer)){
|
||||
|
||||
double dx = target.posX - attacker.posX;
|
||||
double dz = target.posZ - attacker.posZ;
|
||||
|
||||
List<Spell> spells = new ArrayList<Spell>(caster.getSpells());
|
||||
|
||||
if(spells.size() > 0){
|
||||
|
||||
if(!attacker.worldObj.isRemote){
|
||||
|
||||
// New way of choosing a spell; keeps trying until one works or all have been tried
|
||||
|
||||
Spell spell;
|
||||
|
||||
while(!spells.isEmpty()){
|
||||
|
||||
spell = spells.get(attacker.worldObj.rand.nextInt(spells.size()));
|
||||
|
||||
SpellModifiers modifiers = caster.getModifiers();
|
||||
|
||||
if(spell != null && spell.cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){
|
||||
|
||||
if(spell.isContinuous){
|
||||
// -1 because the spell has been cast once already!
|
||||
this.continuousSpellTimer = this.continuousSpellDuration - 1;
|
||||
setContinuousSpellAndNotify(spell, modifiers);
|
||||
|
||||
}else{
|
||||
// For now, the cooldown is just added to the constant base cooldown. I think this
|
||||
// is a reasonable way of doing things; it's certainly better than before.
|
||||
this.cooldown = this.baseCooldown + spell.cooldown;
|
||||
|
||||
if(spell.doesSpellRequirePacket()){
|
||||
// Sends a packet to all players in dimension to tell them to spawn particles.
|
||||
IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers);
|
||||
WizardryPacketHandler.net.sendToDimension(msg, attacker.worldObj.provider.getDimension());
|
||||
}
|
||||
}
|
||||
|
||||
attacker.rotationYaw = (float)(Math.atan2(dz, dx) * 180.0D / Math.PI) - 90.0F;
|
||||
|
||||
return;
|
||||
|
||||
}else{
|
||||
spells.remove(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else if(this.cooldown < 0){
|
||||
// This should only be reached when the entity first starts attacking. Stops it attacking instantly.
|
||||
this.cooldown = this.baseCooldown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
|
||||
public class EntityAISelectSpell extends EntityAIBase {
|
||||
|
||||
// TODO: Write this class
|
||||
|
||||
@Override
|
||||
public boolean shouldExecute(){
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntityBlazeMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntityBlazeMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntityBlaze overrides
|
||||
|
||||
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
|
||||
// targeting system with one that targets hostile mobs and takes the ADS into account.
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
super.initEntityAI();
|
||||
this.targetTasks.taskEntries.clear();
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
/** Normally this would be private, but since this class has subclasses with different spawn/despawn particle
|
||||
* effects, it makes sense to have them override this rather than both onSpawn() and onDespawn(). */
|
||||
protected void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityDecoy extends EntitySummonedCreature {
|
||||
|
||||
public EntityDecoy(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityDecoy(World world, double x, double y, double z, EntityLivingBase caster, int lifetime) {
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.setAlwaysRenderNameTag(caster instanceof EntityPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI() {
|
||||
// Decoys just wander around aimlessly, watching anything living.
|
||||
this.tasks.addTask(0, new EntityAISwimming(this));
|
||||
this.tasks.addTask(1, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(2, new EntityAIWatchClosest(this, EntityLivingBase.class, 6.0F));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
super.onDespawn();
|
||||
for(int i=0; i<20; i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, worldObj, this.posX + (this.rand.nextDouble()-0.5)*this.width,
|
||||
this.getEntityBoundingBox().minY + this.rand.nextDouble()*this.height, this.posZ + (this.rand.nextDouble()-0.5)*this.width,
|
||||
0, 0, 0, 40, 0.2f, 1.0f, 0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntityInvulnerable(DamageSource source){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
super.onUpdate();
|
||||
if(this.getCaster() == null || this.getCaster().isDead){
|
||||
this.setDead();
|
||||
this.onDespawn();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes()
|
||||
{
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.25D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() instanceof EntityPlayer){
|
||||
return this.getCaster().getDisplayName();
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
return getCaster() instanceof EntityPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
super.writeSpawnData(data);
|
||||
if(this.getCaster() != null) data.writeInt(this.getCaster().getEntityId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
super.readSpawnData(data);
|
||||
if(!data.isReadable()) return;
|
||||
this.setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)this.worldObj.getEntityByID(data.readInt())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryAchievements;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAIRestrictOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest2;
|
||||
import net.minecraft.entity.monster.EntityMob;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagInt;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
import net.minecraft.pathfinding.PathNavigateGround;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntityAdditionalSpawnData {
|
||||
|
||||
private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell(this, 0.5D, 14.0F, 30, 50);
|
||||
|
||||
public int textureIndex = 0;
|
||||
|
||||
public boolean hasTower = false;
|
||||
|
||||
/** The entity selector passed into the new AI methods. */
|
||||
protected Predicate<Entity> targetSelector;
|
||||
|
||||
/** Data parameter for the cooldown time for wizards healing themselves. */
|
||||
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
|
||||
/** Data parameter for the wizard's element. */
|
||||
private static final DataParameter<Integer> ELEMENT = EntityDataManager.createKey(EntityEvilWizard.class, DataSerializers.VARINT);
|
||||
/** The resource location for the evil wizard's loot table. */
|
||||
private static final ResourceLocation LOOT_TABLE = new ResourceLocation(Wizardry.MODID, "entities/evil_wizard");
|
||||
|
||||
// Field implementations
|
||||
private List<Spell> spells = new ArrayList<Spell>(4);
|
||||
private Spell continuousSpell;
|
||||
|
||||
public EntityEvilWizard(World world){
|
||||
|
||||
super(world);
|
||||
this.setSize(0.6F, 1.8F);
|
||||
((PathNavigateGround)this.getNavigator()).setBreakDoors(true);
|
||||
|
||||
// For some reason this can't be done in initEntityAI
|
||||
this.tasks.addTask(3, this.spellCastingAI);
|
||||
|
||||
this.detachHome();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit(){
|
||||
super.entityInit();
|
||||
this.dataManager.register(HEAL_COOLDOWN, -1);
|
||||
this.dataManager.register(ELEMENT, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
this.tasks.addTask(0, new EntityAISwimming(this));
|
||||
this.tasks.addTask(4, new EntityAIRestrictOpenDoor(this));
|
||||
this.tasks.addTask(5, new EntityAIOpenDoor(this, true));
|
||||
this.tasks.addTask(6, new EntityAIMoveTowardsRestriction(this, 0.6D));
|
||||
this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 0.6D));
|
||||
|
||||
this.targetSelector = new Predicate<Entity>(){
|
||||
|
||||
public boolean apply(Entity entity){
|
||||
|
||||
// If the target is valid and not invisible...
|
||||
if(entity != null && !entity.isInvisible() && WizardryUtilities.isValidTarget(EntityEvilWizard.this, entity)){
|
||||
|
||||
//... and is a player, a summoned creature, another (non-evil) wizard ...
|
||||
if(entity instanceof EntityPlayer || (entity instanceof ISummonedCreature || entity instanceof EntityWizard
|
||||
// ... or in the whitelist ...
|
||||
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT)))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.targetSelector));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes()
|
||||
{
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.5D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30);
|
||||
}
|
||||
|
||||
private int getHealCooldown(){
|
||||
return this.dataManager.get(HEAL_COOLDOWN);
|
||||
}
|
||||
|
||||
private void setHealCooldown(int cooldown){
|
||||
this.dataManager.set(HEAL_COOLDOWN, cooldown);
|
||||
}
|
||||
|
||||
public Element getElement(){
|
||||
return Element.values()[this.dataManager.get(ELEMENT)];
|
||||
}
|
||||
|
||||
public void setElement(Element element){
|
||||
this.dataManager.set(ELEMENT, element.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return this.spells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
this.continuousSpell = spell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return this.continuousSpell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
super.onLivingUpdate();
|
||||
|
||||
int healCooldown = this.getHealCooldown();
|
||||
|
||||
// This is now done slightly differently because isPotionActive doesn't work on client here, meaning that when
|
||||
// affected with arcane jammer and healCooldown == 0, whilst the wizard didn't actually heal or play the sound,
|
||||
// the particles still spawned, and since healCooldown wasn't reset they spawned every tick until the arcane
|
||||
// jammer wore off.
|
||||
if(healCooldown == 0 && this.getHealth() < this.getMaxHealth() && this.getHealth() > 0 && !this.isPotionActive(WizardryPotions.arcane_jammer)){
|
||||
|
||||
// Healer wizards use greater heal.
|
||||
this.heal(this.getElement() == Element.HEALING ? 8 : 4);
|
||||
this.setHealCooldown(-1);
|
||||
|
||||
// deathTime == 0 checks the wizard isn't currently dying
|
||||
}else if(healCooldown == -1 && this.deathTime == 0){
|
||||
|
||||
// Heal particles
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<10; i++){
|
||||
double d0 = (double)((float)this.posX + rand.nextFloat()*2 - 1.0F);
|
||||
// Apparently the client side spawns the particles 1 block higher than it should... hence the - 0.5F.
|
||||
double d1 = (double)((float)this.posY - 0.5F + rand.nextFloat());
|
||||
double d2 = (double)((float)this.posZ + rand.nextFloat()*2 - 1.0F);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, d0, d1, d2, 0, 0.1F, 0, 48 + rand.nextInt(12), 1.0f, 1.0f, 0.3f);
|
||||
}
|
||||
}else{
|
||||
if(this.getHealth() < 10){
|
||||
// Wizard heals himself more often if he has low health
|
||||
this.setHealCooldown(150);
|
||||
}else{
|
||||
this.setHealCooldown(400);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
}
|
||||
}
|
||||
if(healCooldown > 0){
|
||||
this.setHealCooldown(healCooldown - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack){
|
||||
|
||||
// Debugging
|
||||
//player.addChatComponentMessage(new TextComponentTranslation("wizard.debug", Spell.get(spells[1]).getDisplayName(), Spell.get(spells[2]).getDisplayName(), Spell.get(spells[3]).getDisplayName()));
|
||||
|
||||
// When right-clicked with a spell book in creative, sets one of the spells to that spell
|
||||
if(player.capabilities.isCreativeMode && stack != null && stack.getItem() instanceof ItemSpellBook){
|
||||
if(this.spells.size() >= 4 && Spell.get(stack.getItemDamage()).canBeCastByNPCs()){
|
||||
this.spells.set(rand.nextInt(3)+1, Spell.get(stack.getItemDamage()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbt){
|
||||
super.writeEntityToNBT(nbt);
|
||||
nbt.setInteger("element", this.getElement().ordinal());
|
||||
nbt.setInteger("skin", this.textureIndex);
|
||||
nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id())));
|
||||
nbt.setBoolean("hasTower", this.hasTower);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbt){
|
||||
super.readEntityFromNBT(nbt);
|
||||
this.setElement(Element.values()[nbt.getInteger("element")]);
|
||||
this.textureIndex = nbt.getInteger("skin");
|
||||
this.spells = (List<Spell>) WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
|
||||
(NBTTagInt tag) -> Spell.get(tag.getInt()));
|
||||
this.hasTower = nbt.getBoolean("hasTower");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canDespawn(){
|
||||
// Evil wizards can only despawn if they don't have a tower (i.e. if they spawned naturally at night)
|
||||
return !this.hasTower;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSoundPitch(){
|
||||
return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound() {
|
||||
return SoundEvents.ENTITY_WITCH_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound()
|
||||
{
|
||||
return SoundEvents.ENTITY_WITCH_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound()
|
||||
{
|
||||
return SoundEvents.ENTITY_WITCH_DEATH;
|
||||
}
|
||||
|
||||
// Although it *looks* like this is still called, in actual fact the only method that calls it is overridden in
|
||||
// EntityLiving to use the loot table system instead. This has been kept as a fallback in case the loot table is
|
||||
// not found.
|
||||
@Override
|
||||
protected void dropFewItems(boolean hitByPlayer, int lootingLevel){
|
||||
// Drops 3-5 crystals without looting bonuses
|
||||
int j = 3 + this.rand.nextInt(3) + this.rand.nextInt(1 + lootingLevel);
|
||||
|
||||
for(int k=0; k<j; k++){
|
||||
this.dropItem(WizardryItems.magic_crystal, 1);
|
||||
}
|
||||
|
||||
// Evil wizards occasionally drop one of their spells as a spell book, but not magic missile. This isn't in
|
||||
// the dropRareDrop method because that would be just as rare as normal mobs; instead this is half as rare.
|
||||
if(this.spells.size() > 0 && rand.nextInt(100) - lootingLevel < 5) this.entityDropItem(new ItemStack(WizardryItems.spell_book, 1, this.spells.get(1 + rand.nextInt(this.spells.size() - 1)).id()), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceLocation getLootTable(){
|
||||
return LOOT_TABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(DamageSource source){
|
||||
|
||||
super.onDeath(source);
|
||||
if(source.getEntity() instanceof EntityPlayer){
|
||||
((EntityPlayer)source.getEntity()).addStat(WizardryAchievements.defeat_evil_wizard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData data){
|
||||
|
||||
data = super.onInitialSpawn(difficulty, data);
|
||||
|
||||
textureIndex = this.rand.nextInt(6);
|
||||
|
||||
if(rand.nextBoolean()){
|
||||
this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]);
|
||||
}else{
|
||||
this.setElement(Element.MAGIC);
|
||||
}
|
||||
|
||||
Element element = this.getElement();
|
||||
|
||||
// Adds armour.
|
||||
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
|
||||
this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot)));
|
||||
}
|
||||
|
||||
// Default chance is 0.085f, for reference.
|
||||
for(EntityEquipmentSlot slot : EntityEquipmentSlot.values()) this.setDropChance(slot, 0.0f);
|
||||
|
||||
// All wizards know magic missile, even if it is disabled.
|
||||
spells.add(Spells.magic_missile);
|
||||
|
||||
Tier maxTier = EntityWizard.populateSpells(spells, element, 3, rand);
|
||||
|
||||
// Now done after the spells so it can take the tier into account. For evil wizards this is slightly different;
|
||||
// it picks a random wand which is at least a high enough tier for the spells the wizard has.
|
||||
Tier tier = Tier.values()[maxTier.ordinal() + rand.nextInt(Tier.values().length - maxTier.ordinal())];
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryUtilities.getWand(tier, element)));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
data.writeInt(textureIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
textureIndex = data.readInt();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.monster.EntityIronGolem;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.village.Village;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntityIceGiant(World world){
|
||||
super(world);
|
||||
this.setSize(1.4F, 2.9F);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntityIceGiant(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setSize(1.4F, 2.9F);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
this.getNavigator().getNodeProcessor().setCanSwim(false);
|
||||
this.tasks.addTask(1, new EntityAIAttackMelee(this, 1.0D, true));
|
||||
this.tasks.addTask(2, new EntityAIMoveTowardsTarget(this, 0.9D, 32.0F));
|
||||
//this.tasks.addTask(4, new EntityAIMoveTowardsRestriction(this, 1.0D));
|
||||
//this.tasks.addTask(5, new EntityAIWander(this, 0.6D));
|
||||
this.tasks.addTask(6, new EntityAIWatchClosest(this, EntityPlayer.class, 6.0F));
|
||||
this.tasks.addTask(7, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// EntityIronGolem overrides
|
||||
|
||||
@Override protected void updateAITasks(){} // Disables home-checking
|
||||
@Override public Village getVillage(){ return null; }
|
||||
@Override public int getHoldRoseTick(){ return 0; }
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f);
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0; i<30; i++){
|
||||
float brightness = 0.5f + (rand.nextFloat()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, this.worldObj, this.posX-1 + rand.nextDouble()*2, this.posY+ rand.nextDouble()*3, this.posZ-1 + rand.nextDouble()*2, 0, -0.02, 0, 12 + rand.nextInt(8), brightness, brightness + 0.1f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
super.onLivingUpdate();
|
||||
|
||||
if(this.worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, this.worldObj, this.posX-1 + rand.nextDouble()*2, this.posY+ rand.nextDouble()*3, this.posZ-1 + rand.nextDouble()*2, 0, -0.02, 0, 40 + rand.nextInt(10));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target){
|
||||
|
||||
target.motionY += 0.2;
|
||||
target.motionX += this.getLookVec().xCoord*0.2;
|
||||
target.motionZ += this.getLookVec().xCoord*0.2;
|
||||
|
||||
target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 0));
|
||||
|
||||
this.applyEnchantments(this, target);
|
||||
|
||||
this.playSound(SoundEvents.ENTITY_IRONGOLEM_ATTACK, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom onDespawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityIceWraith extends EntityBlazeMinion {
|
||||
|
||||
/** The version from EntityLivingBase is only used in onLivingUpdate, so it can safely be copied. */
|
||||
private int jumpTicks;
|
||||
|
||||
public EntityIceWraith(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityIceWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.isImmuneToFire = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
super.initEntityAI();
|
||||
this.tasks.taskEntries.clear();
|
||||
this.tasks.addTask(4, new AIIceShardAttack(this));
|
||||
this.tasks.addTask(5, new EntityAIMoveTowardsRestriction(this, 1.0D));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(8, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
|
||||
this.tasks.addTask(8, new EntityAILookIdle(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnParticleEffect() {
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
float brightness = 0.5f + (rand.nextFloat()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - 0.5d + rand.nextDouble(), this.posY + this.height/2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, brightness + 0.1f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(!this.onGround && this.motionY < 0.0D){
|
||||
this.motionY *= 0.6D;
|
||||
}
|
||||
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 0.3F + this.rand.nextFloat()/4, this.rand.nextFloat() * 0.7F + 1.4F);
|
||||
}
|
||||
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i = 0; i < 2; ++i){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.CLOUD, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0D, 0.0D, 0.0D);
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces super call.
|
||||
this.livingBaseUpdate();
|
||||
}
|
||||
|
||||
/** Copied from {@link EntityLivingBase#onLivingUpdate()}. The only change is removal of the updateElytra() call
|
||||
* since that's irrelevant here. In actual fact, neither EntityMob nor EntityLiving has any code in its version
|
||||
* of this method that is of use. This isn't exactly ideal, but it's the lesser of two evils since the
|
||||
* alternative is copying the entire EntityBlaze class and its renderer. All to remove one particle effect... */
|
||||
// ... demonstrating why critical methods should delegate any non-critical functionality (like particles) to
|
||||
// separate protected methods.
|
||||
private void livingBaseUpdate(){
|
||||
|
||||
if (this.jumpTicks > 0)
|
||||
{
|
||||
--this.jumpTicks;
|
||||
}
|
||||
|
||||
if (this.newPosRotationIncrements > 0 && !this.canPassengerSteer())
|
||||
{
|
||||
double d0 = this.posX + (this.interpTargetX - this.posX) / (double)this.newPosRotationIncrements;
|
||||
double d1 = this.posY + (this.interpTargetY - this.posY) / (double)this.newPosRotationIncrements;
|
||||
double d2 = this.posZ + (this.interpTargetZ - this.posZ) / (double)this.newPosRotationIncrements;
|
||||
double d3 = MathHelper.wrapDegrees(this.interpTargetYaw - (double)this.rotationYaw);
|
||||
this.rotationYaw = (float)((double)this.rotationYaw + d3 / (double)this.newPosRotationIncrements);
|
||||
this.rotationPitch = (float)((double)this.rotationPitch + (this.interpTargetPitch - (double)this.rotationPitch) / (double)this.newPosRotationIncrements);
|
||||
--this.newPosRotationIncrements;
|
||||
this.setPosition(d0, d1, d2);
|
||||
this.setRotation(this.rotationYaw, this.rotationPitch);
|
||||
}
|
||||
else if (!this.isServerWorld())
|
||||
{
|
||||
this.motionX *= 0.98D;
|
||||
this.motionY *= 0.98D;
|
||||
this.motionZ *= 0.98D;
|
||||
}
|
||||
|
||||
if (Math.abs(this.motionX) < 0.003D)
|
||||
{
|
||||
this.motionX = 0.0D;
|
||||
}
|
||||
|
||||
if (Math.abs(this.motionY) < 0.003D)
|
||||
{
|
||||
this.motionY = 0.0D;
|
||||
}
|
||||
|
||||
if (Math.abs(this.motionZ) < 0.003D)
|
||||
{
|
||||
this.motionZ = 0.0D;
|
||||
}
|
||||
|
||||
this.worldObj.theProfiler.startSection("ai");
|
||||
|
||||
if (this.isMovementBlocked())
|
||||
{
|
||||
this.isJumping = false;
|
||||
this.moveStrafing = 0.0F;
|
||||
this.moveForward = 0.0F;
|
||||
this.randomYawVelocity = 0.0F;
|
||||
}
|
||||
else if (this.isServerWorld())
|
||||
{
|
||||
this.worldObj.theProfiler.startSection("newAi");
|
||||
this.updateEntityActionState();
|
||||
this.worldObj.theProfiler.endSection();
|
||||
}
|
||||
|
||||
this.worldObj.theProfiler.endSection();
|
||||
this.worldObj.theProfiler.startSection("jump");
|
||||
|
||||
if (this.isJumping)
|
||||
{
|
||||
if (this.isInWater())
|
||||
{
|
||||
this.handleJumpWater();
|
||||
}
|
||||
else if (this.isInLava())
|
||||
{
|
||||
this.handleJumpLava();
|
||||
}
|
||||
else if (this.onGround && this.jumpTicks == 0)
|
||||
{
|
||||
this.jump();
|
||||
this.jumpTicks = 10;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.jumpTicks = 0;
|
||||
}
|
||||
|
||||
this.worldObj.theProfiler.endSection();
|
||||
this.worldObj.theProfiler.startSection("travel");
|
||||
this.moveStrafing *= 0.98F;
|
||||
this.moveForward *= 0.98F;
|
||||
this.randomYawVelocity *= 0.9F;
|
||||
this.moveEntityWithHeading(this.moveStrafing, this.moveForward);
|
||||
this.worldObj.theProfiler.endSection();
|
||||
this.worldObj.theProfiler.startSection("push");
|
||||
this.collideWithNearbyEntities();
|
||||
this.worldObj.theProfiler.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float amount){
|
||||
// Removes the damage from being wet that applies to blazes by checking if the mob is actually drowning.
|
||||
if(source == DamageSource.drown && (this.getAir() > 0 || this.isPotionActive(MobEffects.WATER_BREATHING))){
|
||||
// In this case, the ice wraith is not actually drowning, so cancel the damage.
|
||||
return false;
|
||||
}else{
|
||||
return super.attackEntityFrom(source, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurning(){
|
||||
// Uses the datawatcher on both sides because fire is private to Entity (and I'm not using reflection here).
|
||||
// TESTME: This should work, but there may be some issues with updating, so if it doesn't work, copy the
|
||||
// version from Entity and use reflection to access the fire field.
|
||||
return this.getFlag(0);
|
||||
}
|
||||
|
||||
/** Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball
|
||||
* spawning with a one-liner call to WizardryRegistry.iceShard.cast(...) and the removal of redundant local variables. */
|
||||
static class AIIceShardAttack extends EntityAIBase {
|
||||
|
||||
private final EntityBlaze blaze;
|
||||
private int attackStep;
|
||||
private int attackTime;
|
||||
|
||||
public AIIceShardAttack(EntityBlaze blazeIn)
|
||||
{
|
||||
this.blaze = blazeIn;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the EntityAIBase should begin execution.
|
||||
*/
|
||||
public boolean shouldExecute()
|
||||
{
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
return entitylivingbase != null && entitylivingbase.isEntityAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a one shot task or start executing a continuous task
|
||||
*/
|
||||
public void startExecuting()
|
||||
{
|
||||
this.attackStep = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the task
|
||||
*/
|
||||
public void resetTask()
|
||||
{
|
||||
// This might be called setOnFire, but what it really controls is whether the wraith is in attack mode.
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the task
|
||||
*/
|
||||
public void updateTask()
|
||||
{
|
||||
--this.attackTime;
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
double d0 = this.blaze.getDistanceSqToEntity(entitylivingbase);
|
||||
|
||||
if (d0 < 4.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
this.attackTime = 20;
|
||||
this.blaze.attackEntityAsMob(entitylivingbase);
|
||||
}
|
||||
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
else if (d0 < 256.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
++this.attackStep;
|
||||
|
||||
if (this.attackStep == 1)
|
||||
{
|
||||
this.attackTime = 60;
|
||||
this.blaze.setOnFire(true);
|
||||
}
|
||||
else if (this.attackStep <= 4)
|
||||
{
|
||||
this.attackTime = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.attackTime = 100;
|
||||
this.attackStep = 0;
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
if(this.attackStep > 1){
|
||||
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
|
||||
Spells.ice_shard.cast(this.blaze.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
|
||||
}
|
||||
}
|
||||
|
||||
this.blaze.getLookHelper().setLookPositionWithEntity(entitylivingbase, 10.0F, 10.0F);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.blaze.getNavigator().clearPathEntity();
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
|
||||
super.updateTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityLightningWraith extends EntityBlazeMinion {
|
||||
|
||||
public EntityLightningWraith(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityLightningWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.isImmuneToFire = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
super.initEntityAI();
|
||||
this.tasks.taskEntries.clear();
|
||||
this.tasks.addTask(4, new AILightningAttack(this));
|
||||
this.tasks.addTask(5, new EntityAIMoveTowardsRestriction(this, 1.0D));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(8, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
|
||||
this.tasks.addTask(8, new EntityAILookIdle(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
float brightness = 0.3f + (rand.nextFloat()/2);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - 0.5d + rand.nextDouble(), this.posY + this.height/2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, brightness + 0.2f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
// Fortunately, lightning wraiths don't replace any of blazes' particle effects or the fire sound, they only
|
||||
// add the sparks, so it's fine to call super here.
|
||||
if(worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0, 3);
|
||||
}
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float amount){
|
||||
// Removes the damage from being wet that applies to blazes by checking if the mob is actually drowning.
|
||||
if(source == DamageSource.drown && (this.getAir() > 0 || this.isPotionActive(MobEffects.WATER_BREATHING))){
|
||||
// In this case, the lightning wraith is not actually drowning, so cancel the damage.
|
||||
return false;
|
||||
}else{
|
||||
return super.attackEntityFrom(source, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurning(){
|
||||
// Uses the datawatcher on both sides because fire is private to Entity (and I'm not using reflection here).
|
||||
// TESTME: This should work, but there may be some issues with updating, so if it doesn't work, copy the
|
||||
// version from Entity and use reflection to access the fire field.
|
||||
return this.getFlag(0);
|
||||
}
|
||||
|
||||
/** Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball
|
||||
* spawning with a one-liner call to WizardryRegistry.arc.cast(...) and the removal of redundant local variables. */
|
||||
static class AILightningAttack extends EntityAIBase {
|
||||
|
||||
private final EntityBlaze blaze;
|
||||
private int attackStep;
|
||||
private int attackTime;
|
||||
|
||||
public AILightningAttack(EntityBlaze blazeIn)
|
||||
{
|
||||
this.blaze = blazeIn;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the EntityAIBase should begin execution.
|
||||
*/
|
||||
public boolean shouldExecute()
|
||||
{
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
return entitylivingbase != null && entitylivingbase.isEntityAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a one shot task or start executing a continuous task
|
||||
*/
|
||||
public void startExecuting()
|
||||
{
|
||||
this.attackStep = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the task
|
||||
*/
|
||||
public void resetTask()
|
||||
{
|
||||
// This might be called setOnFire, but what it really controls is whether the wraith is in attack mode.
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the task
|
||||
*/
|
||||
public void updateTask()
|
||||
{
|
||||
--this.attackTime;
|
||||
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
|
||||
double d0 = this.blaze.getDistanceSqToEntity(entitylivingbase);
|
||||
|
||||
if (d0 < 4.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
this.attackTime = 20;
|
||||
this.blaze.attackEntityAsMob(entitylivingbase);
|
||||
}
|
||||
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
else if (d0 < 256.0D)
|
||||
{
|
||||
if (this.attackTime <= 0)
|
||||
{
|
||||
++this.attackStep;
|
||||
|
||||
if (this.attackStep == 1)
|
||||
{
|
||||
this.attackTime = 60;
|
||||
this.blaze.setOnFire(true);
|
||||
}
|
||||
else if (this.attackStep <= 4)
|
||||
{
|
||||
this.attackTime = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.attackTime = 100;
|
||||
this.attackStep = 0;
|
||||
this.blaze.setOnFire(false);
|
||||
}
|
||||
|
||||
if(this.attackStep > 1){
|
||||
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
|
||||
Spells.arc.cast(this.blaze.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
|
||||
}
|
||||
}
|
||||
|
||||
this.blaze.getLookHelper().setLookPositionWithEntity(entitylivingbase, 10.0F, 10.0F);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.blaze.getNavigator().clearPathEntity();
|
||||
this.blaze.getMoveHelper().setMoveTo(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ, 1.0D);
|
||||
}
|
||||
|
||||
super.updateTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.monster.EntitySlime;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** As of Wizardry 1.2, this is now an ISummonedCreature like the rest of them, and it extends EntitySlime. */
|
||||
public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 200;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
public EntityMagicSlime(World world){
|
||||
super(world);
|
||||
this.setSlimeSize(2); // Needs to be called before setting the experience value to 0
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new magic slime with the given caster and lifetime, riding the given target.
|
||||
* @param world The world that the slime is in.
|
||||
* @param caster The entity that created the slime.
|
||||
* @param target The slime's victim. The slime will automatically start riding this entity.
|
||||
* @param lifetime The number of ticks before the slime bursts.
|
||||
*/
|
||||
public EntityMagicSlime(World world, EntityLivingBase caster, EntityLivingBase target, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(target.posX, target.posY, target.posZ);
|
||||
this.startRiding(target);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.setSlimeSize(2); // Needs to be called before setting the experience value to 0
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySlime overrides
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){} // Has no AI!
|
||||
|
||||
@Override
|
||||
protected void dealDamage(EntityLivingBase entity){} // Handles damage itself
|
||||
|
||||
@Override
|
||||
public void setDead(){
|
||||
// Restores behaviour from Entity, replacing slime splitting behaviour.
|
||||
this.isDead = true;
|
||||
// Makes sure that the undoing in onUpdate won't undo this. For some reason, EntitySlime sets isDead directly
|
||||
// to do the peaceful despawning, which seems odd but is actually rather handy!
|
||||
this.setHealth(0);
|
||||
// Bursting effect
|
||||
for(int i=0; i<30; i++){
|
||||
double x = this.posX - 0.5 + rand.nextDouble();
|
||||
double y = this.posY - 0.5 + rand.nextDouble();
|
||||
double z = this.posZ - 0.5 + rand.nextDouble();
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.SLIME, x, y, z, (x - this.posX)*2, (y - this.posY)*2, (z - this.posZ)*2);
|
||||
}
|
||||
this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 2.5f, 0.6f);
|
||||
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST_FAR, 1.0f, 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){
|
||||
// Removes size randomisation
|
||||
IEntityLivingData data = super.onInitialSpawn(difficulty, livingdata);
|
||||
this.setSlimeSize(2);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float amount){
|
||||
// Immune to suffocation
|
||||
return source == DamageSource.inWall ? false : super.attackEntityFrom(source, amount);
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
// Undoes the despawning on peaceful behaviour. I don't think there's anything in super.onUpdate that sets
|
||||
// isDead other than that, but it's better to do a quick sanity check just to be sure.
|
||||
if(this.isDead && worldObj.getDifficulty() == EnumDifficulty.PEACEFUL && this.getHealth() > 0) this.isDead = false;
|
||||
// Bursts instantly rather than doing the falling over animation.
|
||||
if(this.getHealth() <= 0) this.setDead();
|
||||
|
||||
this.updateDelegate();
|
||||
|
||||
// Damages and slows the slime's victim or makes the slime explode if the victim is dead.
|
||||
if(this.getRidingEntity() != null && this.getRidingEntity() instanceof EntityLivingBase && ((EntityLivingBase)this.getRidingEntity()).getHealth() > 0){
|
||||
if(this.ticksExisted % 16 == 1){
|
||||
this.getRidingEntity().attackEntityFrom(DamageSource.magic, 1);
|
||||
((EntityLivingBase)this.getRidingEntity()).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 20, 2));
|
||||
this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 1.0f, 1.0f);
|
||||
this.squishAmount = 0.5F;
|
||||
}
|
||||
}else{
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom onDespawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
private double AISpeed = 0.5;
|
||||
|
||||
// Can attack for 7 seconds, then must cool down for 3.
|
||||
private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 60, 140);
|
||||
|
||||
private Spell continuousSpell;
|
||||
|
||||
private static final List<Spell> attack = Collections.singletonList(Spells.flame_ray);
|
||||
|
||||
public EntityPhoenix(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityPhoenix(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
this.isImmuneToFire = true;
|
||||
this.height = 2.0f;
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(1, this.spellAttackAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(0, new EntityAIWatchClosest(this, EntityLivingBase.class, 0));
|
||||
//this.tasks.addTask(2, new EntityAIWander(this, AISpeed));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
//this.targetTasks.addTask(0, new EntityAIMoveTowardsTarget(this, 1, 10));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
|
||||
this.setAIMoveSpeed((float)AISpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return attack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return continuousSpell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
continuousSpell = spell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
// Makes the flames come from the phoenix's head rather than its body
|
||||
public float getEyeHeight(){
|
||||
return 2.1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getAttributeMap().registerAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(30.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float par1){
|
||||
return 15728880;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float par1){
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
// Makes the phoenix hover.
|
||||
int floorLevel = WizardryUtilities.getNearestFloorLevel(worldObj, new BlockPos(this), 4);
|
||||
|
||||
if(this.posY - floorLevel > 3){
|
||||
this.motionY = -0.1;
|
||||
}else if(this.posY - floorLevel < 2){
|
||||
this.motionY = 0.1;
|
||||
}else{
|
||||
this.motionY = 0.0;
|
||||
}
|
||||
|
||||
// Living sound
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
// Flapping sound effect
|
||||
if(this.ticksExisted % 22 == 0){
|
||||
this.playSound(SoundEvents.ENTITY_ENDERDRAGON_FLAP, 1.0F, 1.0f);
|
||||
}
|
||||
|
||||
for(int i=0; i<2; i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.FLAME, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.height/2 + this.rand.nextDouble() * (double)this.height/2, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0D, -0.1D, 0.0D);
|
||||
}
|
||||
|
||||
// Adding this allows the phoenix to attack despite being in the air. However, for some strange reason
|
||||
// it will only attack when within about 3 blocks of the ground. Any higher and it just sits there, not even
|
||||
// attempting to find targets.
|
||||
|
||||
this.onGround = true;
|
||||
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier){} // Immune to fall damage
|
||||
|
||||
@Override
|
||||
public boolean isBurning()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class EntityShadowWraith extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
// TODO: This currently doesn't fly like it used to. Should it, or does it not matter?
|
||||
|
||||
private double AISpeed = 1.0;
|
||||
|
||||
private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0);
|
||||
|
||||
private static final List<Spell> attack = Collections.singletonList(Spells.darkness_orb);
|
||||
|
||||
public EntityShadowWraith(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityShadowWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(0, this.spellAttackAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(1, new EntityAIAttackMelee(this, AISpeed, false));
|
||||
this.tasks.addTask(2, new EntityAIWander(this, AISpeed));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
|
||||
this.setAIMoveSpeed((float)AISpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return attack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return Spells.none;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
// Doesn't use continuous spells.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getAttributeMap().registerAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(AISpeed);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(16.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPotionApplicable(PotionEffect potion){
|
||||
return potion.getPotion() == MobEffects.WITHER ? false : super.isPotionApplicable(potion);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float partialTicks){
|
||||
return 15728880;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks){
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
float brightness = rand.nextFloat()*0.4f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - 0.5d + rand.nextDouble(), this.posY + this.height/2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, 0.0f, brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
// Slow fall
|
||||
if(!this.onGround && this.motionY < 0.0D){
|
||||
this.motionY *= 0.6D;
|
||||
}
|
||||
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<2; i++){
|
||||
worldObj.spawnParticle(EnumParticleTypes.PORTAL, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0);
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0);
|
||||
float brightness = rand.nextFloat()*0.2f;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0.05f, 0, 20 + rand.nextInt(10), brightness, 0.0f, brightness);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier){
|
||||
// Immune to fall damage.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.monster.EntitySilverfish;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySilverfishMinion extends EntitySilverfish implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySilverfishMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySilverfishMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySilverfish overrides
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
// Super not called because we don't want AISummonSilverfish or AIHideInStone
|
||||
this.tasks.addTask(1, new EntityAISwimming(this));
|
||||
this.tasks.addTask(4, new EntityAIAttackMelee(this, 1.0D, false));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0.0d, 0.0d, 0.0d, 0, 0.3f, 0.3f, 0.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target){
|
||||
if(!target.isEntityAlive()){
|
||||
this.onKillEntity(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKillEntity(EntityLivingBase victim) {
|
||||
// If the silverfish has a summoner, this is actually called from Wizardry's event handler rather than by
|
||||
// Minecraft itself, because the damagesource being changed causes it not to get called.
|
||||
if(!this.worldObj.isRemote){
|
||||
// Summons 1-4 more silverfish
|
||||
int alliesToSummon = rand.nextInt(4) + 1;
|
||||
|
||||
for(int i=0; i<alliesToSummon; i++){
|
||||
EntitySilverfishMinion silverfish = new EntitySilverfishMinion(this.worldObj, victim.posX, victim.posY, victim.posZ, this.getCaster(), this.lifetime);
|
||||
this.worldObj.spawnEntityInWorld(silverfish);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.attributes.AttributeModifier;
|
||||
import net.minecraft.entity.monster.EntitySkeleton;
|
||||
import net.minecraft.entity.monster.SkeletonType;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySkeletonMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySkeletonMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySkeleton overrides
|
||||
|
||||
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
|
||||
// targeting system with one that targets hostile mobs and takes the ADS into account.
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
super.initEntityAI();
|
||||
this.targetTasks.taskEntries.clear();
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// Shouldn't have randomised armour, but does still need a bow!
|
||||
@Override
|
||||
protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty) {
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW));
|
||||
}
|
||||
|
||||
// Where the skeleton minion is summoned does not affect its type.
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata)
|
||||
{
|
||||
// Can't call super, so the code from the next level up (EntityLiving) had to be copied as well.
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1));
|
||||
|
||||
if (this.rand.nextFloat() < 0.05F)
|
||||
{
|
||||
this.setLeftHanded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setLeftHanded(false);
|
||||
}
|
||||
|
||||
// Halloween pumpkin heads! Why not?
|
||||
if (this.getItemStackFromSlot(EntityEquipmentSlot.HEAD) == null)
|
||||
{
|
||||
Calendar calendar = this.worldObj.getCurrentDate();
|
||||
|
||||
if (calendar.get(2) + 1 == 10 && calendar.get(5) == 31 && this.rand.nextFloat() < 0.25F)
|
||||
{
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.HEAD, new ItemStack(this.rand.nextFloat() < 0.1F ? Blocks.LIT_PUMPKIN : Blocks.PUMPKIN));
|
||||
this.inventoryArmorDropChances[EntityEquipmentSlot.HEAD.getIndex()] = 0.0F;
|
||||
}
|
||||
}
|
||||
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target) {
|
||||
if(this.getSkeletonType() == SkeletonType.WITHER){
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.WITHER, 200));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.attributes.AttributeModifier;
|
||||
import net.minecraft.entity.monster.EntityCaveSpider;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.EnumDifficulty;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySpiderMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySpiderMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntitySpider overrides
|
||||
|
||||
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
|
||||
// targeting system with one that targets hostile mobs and takes the ADS into account.
|
||||
@Override
|
||||
protected void initEntityAI()
|
||||
{
|
||||
super.initEntityAI();
|
||||
this.targetTasks.taskEntries.clear();
|
||||
// Spiders use a custom AI type specific to spiders which I can't access, but it's just an extension of
|
||||
// EntityAINearestAttackableTarget which takes daylight into account. Since I want spider minions to attack
|
||||
// regardless of daylight, I can just use EntityAINearestAttackableTarget.
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
// No spider jockeys!
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata) {
|
||||
|
||||
// Can't call super, so the code from the next level up (EntityLiving) had to be copied as well.
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1));
|
||||
|
||||
if (this.rand.nextFloat() < 0.05F)
|
||||
{
|
||||
this.setLeftHanded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setLeftHanded(false);
|
||||
}
|
||||
|
||||
// Don't need anything from EntitySpider, since neither spider jockeys nor group data is relevant.
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, worldObj, this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.2f, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulAttack(EntityLivingBase target){
|
||||
|
||||
int seconds = 0;
|
||||
|
||||
if(this.worldObj.getDifficulty() == EnumDifficulty.NORMAL){
|
||||
seconds = 7;
|
||||
}else if(this.worldObj.getDifficulty() == EnumDifficulty.HARD){
|
||||
seconds = 15;
|
||||
}
|
||||
|
||||
if(seconds > 0){
|
||||
target.addPotionEffect(new PotionEffect(MobEffects.POISON, seconds * 20, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.passive.EntityAnimal;
|
||||
import net.minecraft.entity.passive.EntityHorse;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.util.text.translation.I18n;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** Does not implement ISummonedCreature because it has different despawning rules and because EntityHorse already
|
||||
* has an owner system. */
|
||||
@SuppressWarnings("deprecation") // It's what Entity does, so...
|
||||
public class EntitySpiritHorse extends EntityHorse {
|
||||
|
||||
private int idleTimer = 0;
|
||||
|
||||
public EntitySpiritHorse(World par1World)
|
||||
{
|
||||
super(par1World);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
if (this.hasCustomName())
|
||||
{
|
||||
return this.getCustomNameTag();
|
||||
}
|
||||
else
|
||||
{
|
||||
return I18n.translateToLocal("entity.wizardry.Spirit Horse.name");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChested()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalArmorValue()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getExperiencePoints(EntityPlayer p_70693_1_){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Item getDropItem(){
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dropFewItems(boolean par1, int par2){}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes()
|
||||
{
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(24.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openGUI(EntityPlayer p_110199_1_){}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack){
|
||||
|
||||
ItemStack itemstack = player.inventory.getCurrentItem();
|
||||
|
||||
// Allows the owner (but not other players) to dispel the spirit horse using a wand (shift-clicking, because clicking mounts the horse in this case).
|
||||
if(itemstack != null && itemstack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
|
||||
// Prevents accidental double clicking.
|
||||
if(this.ticksExisted > 20){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
this.setDead();
|
||||
if(WizardData.get(player) != null){
|
||||
WizardData.get(player).hasSpiritHorse = false;
|
||||
}
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
// This is necessary to prevent the wand's spell being cast when performing this action.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.processInteract(player, hand, itemstack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(DamageSource par1DamageSource){
|
||||
|
||||
super.onDeath(par1DamageSource);
|
||||
|
||||
// Allows player to summon another spirit horse once this one has died.
|
||||
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
|
||||
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false;
|
||||
}
|
||||
}
|
||||
|
||||
// I wrote this one!
|
||||
private EntityLivingBase getOwner(){
|
||||
|
||||
// I think the DataManager stores any objects, so it now stores the UUID instead of its string representation.
|
||||
Entity owner = WizardryUtilities.getEntityByUUID(worldObj, this.getOwnerUniqueId());
|
||||
|
||||
if(owner instanceof EntityLivingBase){
|
||||
return (EntityLivingBase)owner;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Adds a dust particle effect
|
||||
if(this.worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 0, 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
|
||||
// Spirit horse disappears a short time after being dismounted.
|
||||
if(!this.isBeingRidden()){
|
||||
this.idleTimer++;
|
||||
}else if(this.idleTimer > 0){
|
||||
this.idleTimer = 0;
|
||||
}
|
||||
|
||||
if(this.idleTimer > 200){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
// Allows player to summon another spirit horse once this one has disappeared.
|
||||
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
|
||||
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false;
|
||||
}
|
||||
this.setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMateWith(EntityAnimal par1EntityAnimal){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData data){
|
||||
|
||||
// Adds Particles on spawn. Due to client/server differences this cannot be done in the item.
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return super.onInitialSpawn(difficulty, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getOwner() != null){
|
||||
return new TextComponentTranslation(ISummonedCreature.NAMEPLATE_TRANSLATION_KEY, getOwner().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getOwner() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityAgeable;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIFollowOwner;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILeapAtTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAIOwnerHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAIOwnerHurtTarget;
|
||||
import net.minecraft.entity.ai.EntityAISit;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.passive.EntityWolf;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** Does not implement ISummonedCreature because it has different despawning rules and because EntityWolf already
|
||||
* has an owner system. */
|
||||
public class EntitySpiritWolf extends EntityWolf {
|
||||
|
||||
public EntitySpiritWolf(World par1World){
|
||||
|
||||
super(par1World);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.aiSit = new EntityAISit(this);
|
||||
this.tasks.addTask(1, new EntityAISwimming(this));
|
||||
this.tasks.addTask(2, this.aiSit);
|
||||
this.tasks.addTask(3, new EntityAILeapAtTarget(this, 0.4F));
|
||||
this.tasks.addTask(4, new EntityAIAttackMelee(this, 1.0D, true));
|
||||
this.tasks.addTask(5, new EntityAIFollowOwner(this, 1.0D, 10.0F, 2.0F));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 1.0D));
|
||||
this.tasks.addTask(9, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
|
||||
this.tasks.addTask(9, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIOwnerHurtByTarget(this));
|
||||
this.targetTasks.addTask(2, new EntityAIOwnerHurtTarget(this));
|
||||
this.targetTasks.addTask(3, new EntityAIHurtByTarget(this, true, new Class[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(DamageSource source){
|
||||
|
||||
// Allows player to summon another spirit wolf once this one has died.
|
||||
// NOTE: This has been known to work incorrectly.
|
||||
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
|
||||
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritWolf = false;
|
||||
}
|
||||
|
||||
super.onDeath(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getExperiencePoints(EntityPlayer p_70693_1_){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata) {
|
||||
|
||||
// Adds Particles on spawn. Due to client/server differences this cannot be done in the item.
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
|
||||
super.onUpdate();
|
||||
|
||||
// Adds a dust particle effect
|
||||
if(this.worldObj.isRemote){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 0, 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
|
||||
if (this.isTamed())
|
||||
{
|
||||
if (stack != null){
|
||||
|
||||
// Allows the owner (but not other players) to dispel the spirit wolf using a wand.
|
||||
if(stack != null && stack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
|
||||
// Prevents accidental double clicking.
|
||||
if(this.ticksExisted > 20){
|
||||
for(int i=0;i<10;i++){
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, this.posX - this.width/2 + this.rand.nextFloat()*width, this.posY + this.height*this.rand.nextFloat() + 0.2f, this.posZ - this.width/2 + this.rand.nextFloat()*width, 0, 0, 0, 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f);
|
||||
}
|
||||
this.setDead();
|
||||
if(WizardData.get(player) != null){
|
||||
WizardData.get(player).hasSpiritWolf = false;
|
||||
}
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
// This is necessary to prevent the wand's spell being cast when performing this action.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityWolf createChild(EntityAgeable par1EntityAgeable)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Item getDropItem()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getOwner() != null){
|
||||
return new TextComponentTranslation(ISummonedCreature.NAMEPLATE_TRANSLATION_KEY, getOwner().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getOwner() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIAttackMelee;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookIdle;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.effect.EntityLightningBolt;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
|
||||
public class EntityStormElemental extends EntitySummonedCreature implements ISpellCaster {
|
||||
|
||||
private double AISpeed = 1.0;
|
||||
|
||||
private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0);
|
||||
|
||||
private static final List<Spell> attack = Collections.singletonList(Spells.lightning_disc);
|
||||
|
||||
public EntityStormElemental(World world){
|
||||
super(world);
|
||||
}
|
||||
|
||||
public EntityStormElemental(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world, x, y, z, caster, lifetime);
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(0, this.spellAttackAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(1, new EntityAIAttackMelee(this, AISpeed, false));
|
||||
this.tasks.addTask(2, new EntityAIWander(this, AISpeed));
|
||||
this.tasks.addTask(3, new EntityAILookIdle(this));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
|
||||
this.setAIMoveSpeed((float)AISpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRangedAttack() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return attack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return Spells.none;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
// Doesn't use continuous spells.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getAttributeMap().registerAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(AISpeed);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30.0D);
|
||||
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(16.0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_AMBIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_HURT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound(){
|
||||
return SoundEvents.ENTITY_BLAZE_DEATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public int getBrightnessForRender(float partialTicks){
|
||||
return 15728880;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(float partialTicks){
|
||||
return 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
if(this.ticksExisted % 120 == 1){
|
||||
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
if (this.rand.nextInt(24) == 0){
|
||||
this.playSound(SoundEvents.ENTITY_BLAZE_BURN, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F);
|
||||
}
|
||||
|
||||
// Slow fall
|
||||
if(!this.onGround && this.motionY < 0.0D){
|
||||
this.motionY *= 0.6D;
|
||||
}
|
||||
|
||||
if(worldObj.isRemote){
|
||||
|
||||
for(int i=0; i<2; ++i){
|
||||
worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, worldObj, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
for(int i=0; i<10; i++){
|
||||
float brightness = rand.nextFloat()*0.2f;
|
||||
double dy = this.rand.nextDouble() * (double)this.height;
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE_ROTATING, worldObj, this.posX, this.posY + dy, this.posZ, 0, 0, 0, 20 + rand.nextInt(10), 0, brightness, brightness, false, 0.2f + 0.5f*dy);
|
||||
}
|
||||
}
|
||||
|
||||
super.onLivingUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall(float distance, float damageMultiplier){
|
||||
// Immune to fall damage.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStruckByLightning(EntityLightningBolt lightning){
|
||||
// Immune to lightning.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityCreature;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/** Abstract base implementation of {@link ISummonedCreature} which is the superclass to all custom summoned entities
|
||||
* (i.e. entities that don't extend vanilla/mod creatures). Also serves as an example of how to correctly implement
|
||||
* the above interface, and includes some non-critical method overrides which should be used for best results (xp, drops,
|
||||
* and such like). <i>Not to be confused with the old version of EntitySummonedCreature; that system has been replaced.</i>
|
||||
* @since Wizardry 1.2
|
||||
* @author Electroblob */
|
||||
public abstract class EntitySummonedCreature extends EntityCreature implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntitySummonedCreature(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntitySummonedCreature(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom onDespawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity and this entity only has melee attacks.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType) || this.hasRangedAttack();
|
||||
}
|
||||
|
||||
// TODO: Backport the following two methods to 1.7.10.
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
// Specific to EntitySummonedCreature, remove if copying
|
||||
|
||||
/** Whether this summoned creature has a ranged attack. Used to test whether it should attack flying creatures. */
|
||||
public abstract boolean hasRangedAttack();
|
||||
}
|
||||
@@ -0,0 +1,803 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.item.ItemSpellBook;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryAchievements;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.registry.WizardrySounds;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WandHelper;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityAgeable;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.IEntityLivingData;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAILookAtTradePlayer;
|
||||
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.ai.EntityAIOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAIRestrictOpenDoor;
|
||||
import net.minecraft.entity.ai.EntityAISwimming;
|
||||
import net.minecraft.entity.ai.EntityAITradePlayer;
|
||||
import net.minecraft.entity.ai.EntityAIWander;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest;
|
||||
import net.minecraft.entity.ai.EntityAIWatchClosest2;
|
||||
import net.minecraft.entity.effect.EntityLightningBolt;
|
||||
import net.minecraft.entity.monster.IMob;
|
||||
import net.minecraft.entity.passive.EntityVillager;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagInt;
|
||||
import net.minecraft.nbt.NBTTagLong;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.network.datasync.DataSerializers;
|
||||
import net.minecraft.network.datasync.EntityDataManager;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.village.MerchantRecipe;
|
||||
import net.minecraft.village.MerchantRecipeList;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
public class EntityWizard extends EntityVillager implements ISpellCaster, IEntityAdditionalSpawnData {
|
||||
|
||||
/*
|
||||
* After much debugging, the error in the compiled mod (outside of eclipse) was traced back to this class,
|
||||
* specifically the methods copied in from EntityVillager when I changed this class to extend it. This figures,
|
||||
* since I had 1.2.1 working just fine before I did that, and it was the only thing I changed. Apparently,
|
||||
* methods and fields with obfuscated names like func_129090_a can cause problems when compiled. One of the
|
||||
* ones here was renamed and the other deleted since it was never called. Watch out for this in future (unless,
|
||||
* of course, they are overriding something, in which case it should be fine).
|
||||
*/
|
||||
|
||||
// Extending EntityVillager turned out to be a pretty neat thing to do, since now zombies will attack wizards
|
||||
|
||||
private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell(this, 0.5D, 14.0F, 30, 50);
|
||||
|
||||
public int textureIndex = 0;
|
||||
|
||||
/** The entity selector passed into the new AI methods. */
|
||||
protected Predicate<Entity> targetSelector;
|
||||
|
||||
/** Copy of EntityVillager's buyingList, renamed to avoid confusion. */
|
||||
private MerchantRecipeList trades;
|
||||
private int timeUntilReset;
|
||||
|
||||
/** addDefaultEquipmentAndRecipies is called if this is true */
|
||||
private boolean updateRecipes;
|
||||
|
||||
/** Data parameter for the cooldown time for wizards healing themselves. */
|
||||
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
|
||||
/** Data parameter for the wizard's element. */
|
||||
private static final DataParameter<Integer> ELEMENT = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
|
||||
|
||||
// Field implementations
|
||||
private List<Spell> spells = new ArrayList<Spell>(4);
|
||||
private Spell continuousSpell;
|
||||
|
||||
/** A set of the positions of the blocks that are part of this wizard's tower. */
|
||||
private Set<BlockPos> towerBlocks;
|
||||
|
||||
public EntityWizard(World world){
|
||||
super(world);
|
||||
this.detachHome();
|
||||
// For some reason this can't be in initEntityAI
|
||||
this.tasks.addTask(3, this.spellCastingAI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entityInit(){
|
||||
super.entityInit();
|
||||
this.dataManager.register(HEAL_COOLDOWN, -1);
|
||||
this.dataManager.register(ELEMENT, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEntityAI(){
|
||||
|
||||
this.tasks.addTask(0, new EntityAISwimming(this));
|
||||
this.tasks.addTask(1, new EntityAITradePlayer(this));
|
||||
this.tasks.addTask(1, new EntityAILookAtTradePlayer(this));
|
||||
this.tasks.addTask(4, new EntityAIRestrictOpenDoor(this));
|
||||
this.tasks.addTask(5, new EntityAIOpenDoor(this, true));
|
||||
this.tasks.addTask(6, new EntityAIMoveTowardsRestriction(this, 0.6D));
|
||||
this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
|
||||
this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityWizard.class, 5.0F, 0.02F));
|
||||
this.tasks.addTask(7, new EntityAIWander(this, 0.6D));
|
||||
this.tasks.addTask(8, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F));
|
||||
|
||||
this.targetSelector = new Predicate<Entity>(){
|
||||
|
||||
public boolean apply(Entity entity){
|
||||
|
||||
// If the target is valid and not invisible...
|
||||
if(entity != null && !entity.isInvisible() && WizardryUtilities.isValidTarget(EntityWizard.this, entity)){
|
||||
|
||||
//... and is a mob, a summoned creature ...
|
||||
if((entity instanceof IMob || entity instanceof ISummonedCreature
|
||||
// ... or in the whitelist ...
|
||||
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT)))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
|
||||
// By default, wizards don't attack players unless the player has attacked them.
|
||||
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<EntityLiving>(this, EntityLiving.class, 0, false, true, this.targetSelector));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEntityAttributes(){
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.5);
|
||||
}
|
||||
|
||||
private int getHealCooldown(){
|
||||
return this.dataManager.get(HEAL_COOLDOWN);
|
||||
}
|
||||
|
||||
private void setHealCooldown(int cooldown){
|
||||
this.dataManager.set(HEAL_COOLDOWN, cooldown);
|
||||
}
|
||||
|
||||
public Element getElement(){
|
||||
return Element.values()[this.dataManager.get(ELEMENT)];
|
||||
}
|
||||
|
||||
public void setElement(Element element){
|
||||
this.dataManager.set(ELEMENT, element.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spell> getSpells(){
|
||||
return this.spells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellModifiers getModifiers(){
|
||||
return new SpellModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContinuousSpell(Spell spell){
|
||||
this.continuousSpell = spell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spell getContinuousSpell(){
|
||||
return this.continuousSpell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLivingUpdate(){
|
||||
|
||||
super.onLivingUpdate();
|
||||
|
||||
// Still better to store this to a local variable as it's almost certainly more efficient.
|
||||
int healCooldown = this.getHealCooldown();
|
||||
|
||||
// This is now done slightly differently because isPotionActive doesn't work on client here, meaning that when
|
||||
// affected with arcane jammer and healCooldown == 0, whilst the wizard didn't actually heal or play the sound,
|
||||
// the particles still spawned, and since healCooldown wasn't reset they spawned every tick until the arcane
|
||||
// jammer wore off.
|
||||
if(healCooldown == 0 && this.getHealth() < this.getMaxHealth() && this.getHealth() > 0 && !this.isPotionActive(WizardryPotions.arcane_jammer)){
|
||||
|
||||
// Healer wizards use greater heal.
|
||||
this.heal(this.getElement() == Element.HEALING ? 8 : 4);
|
||||
this.setHealCooldown(-1);
|
||||
|
||||
// deathTime == 0 checks the wizard isn't currently dying
|
||||
}else if(healCooldown == -1 && this.deathTime == 0){
|
||||
|
||||
// Heal particles
|
||||
if(worldObj.isRemote){
|
||||
for(int i=0; i<10; i++){
|
||||
double d0 = (double)((float)this.posX + rand.nextFloat()*2 - 1.0F);
|
||||
// Apparently the client side spawns the particles 1 block higher than it should... hence the - 0.5F.
|
||||
double d1 = (double)((float)this.posY - 0.5F + rand.nextFloat());
|
||||
double d2 = (double)((float)this.posZ + rand.nextFloat()*2 - 1.0F);
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, worldObj, d0, d1, d2, 0, 0.1F, 0, 48 + rand.nextInt(12), 1.0f, 1.0f, 0.3f);
|
||||
}
|
||||
}else{
|
||||
if(this.getHealth() < 10){
|
||||
// Wizard heals himself more often if he has low health
|
||||
this.setHealCooldown(150);
|
||||
}else{
|
||||
this.setHealCooldown(400);
|
||||
}
|
||||
|
||||
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
if(healCooldown > 0){
|
||||
this.setHealCooldown(healCooldown-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateAITasks(){
|
||||
|
||||
if(!this.isTrading() && this.timeUntilReset > 0){
|
||||
|
||||
--this.timeUntilReset;
|
||||
|
||||
if(this.timeUntilReset <= 0){
|
||||
|
||||
if(this.updateRecipes){
|
||||
|
||||
for(MerchantRecipe merchantrecipe : this.trades){
|
||||
|
||||
if(merchantrecipe.isRecipeDisabled()){
|
||||
// Increases the number of allowed uses of a disabled recipe by a random number.
|
||||
merchantrecipe.increaseMaxTradeUses(this.rand.nextInt(6) + this.rand.nextInt(6) + 2);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.trades.size() < 12){
|
||||
this.addRandomRecipes(1);
|
||||
}
|
||||
|
||||
this.updateRecipes = false;
|
||||
}
|
||||
|
||||
this.addPotionEffect(new PotionEffect(MobEffects.REGENERATION, 200, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Super call removed because EntityVillager's version does things I don't want and the next one up is
|
||||
// in EntityLivingBase and does nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
|
||||
// Debugging
|
||||
//player.addChatComponentMessage(new TextComponentTranslation("wizard.debug", Spell.get(spells[1]).getDisplayName(), Spell.get(spells[2]).getDisplayName(), Spell.get(spells[3]).getDisplayName()));
|
||||
|
||||
// When right-clicked with a spell book in creative, sets one of the spells to that spell
|
||||
if(player.capabilities.isCreativeMode && stack != null && stack.getItem() instanceof ItemSpellBook){
|
||||
if(this.spells.size() >= 4 && Spell.get(stack.getItemDamage()).canBeCastByNPCs()){
|
||||
this.spells.set(rand.nextInt(3)+1, Spell.get(stack.getItemDamage()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Won't trade with a player that has attacked them.
|
||||
if (this.isEntityAlive() && !this.isTrading() && !this.isChild() && !player.isSneaking() && this.getAttackTarget() != player)
|
||||
{
|
||||
if (!this.worldObj.isRemote)
|
||||
{
|
||||
this.setCustomer(player);
|
||||
player.displayVillagerTradeGui(this);
|
||||
//player.displayGUIMerchant(this, this.getElement().getWizardName());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName() {
|
||||
return this.getElement().getWizardName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbt){
|
||||
|
||||
super.writeEntityToNBT(nbt);
|
||||
|
||||
if (this.trades != null){
|
||||
nbt.setTag("trades", this.trades.getRecipiesAsTags());
|
||||
}
|
||||
|
||||
nbt.setInteger("element", this.getElement().ordinal());
|
||||
nbt.setInteger("skin", this.textureIndex);
|
||||
nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id())));
|
||||
|
||||
if(this.towerBlocks != null && this.towerBlocks.size() > 0){
|
||||
nbt.setTag("towerBlocks", WizardryUtilities.listToNBT(this.towerBlocks, pos -> new NBTTagLong(pos.toLong())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbt){
|
||||
|
||||
super.readEntityFromNBT(nbt);
|
||||
|
||||
if(nbt.hasKey("trades")){
|
||||
NBTTagCompound nbttagcompound1 = nbt.getCompoundTag("trades");
|
||||
this.trades = new MerchantRecipeList(nbttagcompound1);
|
||||
}
|
||||
|
||||
this.setElement(Element.values()[nbt.getInteger("element")]);
|
||||
this.textureIndex = nbt.getInteger("skin");
|
||||
this.spells = (List<Spell>) WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
|
||||
(NBTTagInt tag) -> Spell.get(tag.getInt()));
|
||||
|
||||
this.towerBlocks = new HashSet<BlockPos>(WizardryUtilities.NBTToList(nbt.getTagList("towerBlocks",
|
||||
NBT.TAG_LONG), (NBTTagLong tag) -> BlockPos.fromLong(tag.getLong())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canDespawn(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrading()
|
||||
{
|
||||
return this.getCustomer() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useRecipe(MerchantRecipe merchantrecipe){
|
||||
|
||||
merchantrecipe.incrementToolUses();
|
||||
this.livingSoundTime = -this.getTalkInterval();
|
||||
this.playSound(SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch());
|
||||
|
||||
// Achievements
|
||||
if (this.getCustomer() != null)
|
||||
{
|
||||
this.getCustomer().addStat(WizardryAchievements.wizard_trade);
|
||||
|
||||
if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook
|
||||
&& Spell.get(merchantrecipe.getItemToSell().getItemDamage()).tier == Tier.MASTER){
|
||||
this.getCustomer().addStat(WizardryAchievements.buy_master_spell);
|
||||
}
|
||||
}
|
||||
|
||||
// Changed to a 4 in 5 chance of unlocking a new recipe.
|
||||
if(this.rand.nextInt(5) > 0){
|
||||
this.timeUntilReset = 40;
|
||||
this.updateRecipes = true;
|
||||
|
||||
if (this.getCustomer() != null)
|
||||
{
|
||||
this.getCustomer().getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is called from the gui in order to display the recipes (no surprise there), and this is actually where
|
||||
// the initialisation is done, i.e. the trades don't actually exist until some player goes to trade with the
|
||||
// villager, at which point the first is added.
|
||||
@Override
|
||||
public MerchantRecipeList getRecipes(EntityPlayer par1EntityPlayer){
|
||||
|
||||
if(this.trades == null){
|
||||
|
||||
this.trades = new MerchantRecipeList();
|
||||
|
||||
// All wizards will buy spell books
|
||||
ItemStack anySpellBook = new ItemStack(WizardryItems.spell_book, 1, OreDictionary.WILDCARD_VALUE);
|
||||
ItemStack crystalStack = new ItemStack(WizardryItems.magic_crystal, 5);
|
||||
|
||||
// NOTE: For wizardry 1.2, increase the number of uses of this trade. The default is 7, for reference.
|
||||
this.trades.add(new MerchantRecipe(anySpellBook, crystalStack));
|
||||
|
||||
this.addRandomRecipes(3);
|
||||
}
|
||||
|
||||
return this.trades;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called once on initialisation and then once each time the wizard gains new trades (the particle thingy).
|
||||
*/
|
||||
private void addRandomRecipes(int numberOfItemsToAdd){
|
||||
|
||||
MerchantRecipeList merchantrecipelist;
|
||||
merchantrecipelist = new MerchantRecipeList();
|
||||
|
||||
for(int i=0; i<numberOfItemsToAdd; i++){
|
||||
|
||||
ItemStack itemToSell = null;
|
||||
|
||||
boolean itemAlreadySold = true;
|
||||
|
||||
Tier tier = Tier.BASIC;
|
||||
|
||||
while(itemAlreadySold){
|
||||
|
||||
itemAlreadySold = false;
|
||||
|
||||
/* New way of getting random item, by giving a chance to increase the tier which depends on how much the
|
||||
* player has already traded with the wizard. The more the player has traded with the wizard, the more
|
||||
* likely they are to get items of a higher tier. The -4 is to ignore the original 4 trades.
|
||||
* For reference, the chances are as follows:
|
||||
* Trades done Basic Apprentice Advanced Master
|
||||
* 0 50% 25% 18% 8%
|
||||
* 1 46% 25% 20% 9%
|
||||
* 2 42% 24% 22% 12%
|
||||
* 3 38% 24% 24% 14%
|
||||
* 4 34% 22% 26% 17%
|
||||
* 5 30% 21% 28% 21%
|
||||
* 6 26% 19% 30% 24%
|
||||
* 7 22% 17% 32% 28%
|
||||
* 8 18% 15% 34% 33% */
|
||||
|
||||
double tierIncreaseChance = 0.5 + 0.04*(Math.max(this.trades.size()-4, 0));
|
||||
|
||||
tier = Tier.BASIC;
|
||||
|
||||
if(rand.nextDouble() < tierIncreaseChance){
|
||||
tier = Tier.APPRENTICE;
|
||||
if(rand.nextDouble() < tierIncreaseChance){
|
||||
tier = Tier.ADVANCED;
|
||||
if(rand.nextDouble() < tierIncreaseChance*0.6){
|
||||
tier = Tier.MASTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemToSell = this.getRandomItemOfTier(tier);
|
||||
|
||||
for(Object recipe : merchantrecipelist){
|
||||
if(ItemStack.areItemStacksEqual(((MerchantRecipe)recipe).getItemToSell(), itemToSell)) itemAlreadySold = true;
|
||||
}
|
||||
|
||||
if(this.trades != null){
|
||||
for(Object recipe : this.trades){
|
||||
if(ItemStack.areItemStacksEqual(((MerchantRecipe)recipe).getItemToSell(), itemToSell)) itemAlreadySold = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't know how it can ever be null here, but saves it crashing.
|
||||
if(itemToSell == null) return;
|
||||
|
||||
merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier), new ItemStack(WizardryItems.magic_crystal, tier.ordinal()*3 + 1 + rand.nextInt(4)), itemToSell));
|
||||
}
|
||||
|
||||
Collections.shuffle(merchantrecipelist);
|
||||
|
||||
if (this.trades == null)
|
||||
{
|
||||
this.trades = new MerchantRecipeList();
|
||||
}
|
||||
|
||||
for (int j1 = 0; j1 < merchantrecipelist.size(); ++j1)
|
||||
{
|
||||
this.trades.add(merchantrecipelist.get(j1));
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack getRandomPrice(Tier tier) {
|
||||
ItemStack itemstack = null;
|
||||
switch(this.rand.nextInt(3)){
|
||||
case 0:
|
||||
itemstack = new ItemStack(Items.GOLD_INGOT, (tier.ordinal()+1)*8-1 + rand.nextInt(6));
|
||||
break;
|
||||
case 1:
|
||||
itemstack = new ItemStack(Items.DIAMOND, (tier.ordinal()+1)*4-2 + rand.nextInt(3));
|
||||
break;
|
||||
case 2:
|
||||
itemstack = new ItemStack(Items.EMERALD, (tier.ordinal()+1)*6-1 + rand.nextInt(3));
|
||||
break;
|
||||
}
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
private ItemStack getRandomItemOfTier(Tier tier){
|
||||
|
||||
int randomiser;
|
||||
|
||||
// All enabled spells of the given tier
|
||||
List<Spell> spells = Spell.getSpells(new Spell.TierElementFilter(tier, null));
|
||||
// All enabled spells of the given tier that match this wizard's element
|
||||
List<Spell> specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement()));
|
||||
|
||||
// This code is sooooooo much neater with the new filter system!
|
||||
switch(tier){
|
||||
|
||||
case BASIC:
|
||||
randomiser = rand.nextInt(5);
|
||||
if(randomiser < 4 && !spells.isEmpty()){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){
|
||||
// This means it is more likely for spell books sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
}
|
||||
}else{
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}
|
||||
|
||||
case APPRENTICE:
|
||||
randomiser = rand.nextInt(Wizardry.settings.discoveryMode ? 12 : 10);
|
||||
if(randomiser < 5 && !spells.isEmpty()){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){
|
||||
// This means it is more likely for spell books sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
}
|
||||
}else if(randomiser < 6){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}else if(randomiser < 8){
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 1);
|
||||
}else if(randomiser < 10){
|
||||
EntityEquipmentSlot slot = WizardryUtilities.ARMOUR_SLOTS[rand.nextInt(WizardryUtilities.ARMOUR_SLOTS.length)];
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for armour sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getArmour(this.getElement(), slot));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot));
|
||||
}
|
||||
}else{
|
||||
// Don't need to check for discovery mode here since it is done above
|
||||
return new ItemStack(WizardryItems.identification_scroll);
|
||||
}
|
||||
|
||||
case ADVANCED:
|
||||
randomiser = rand.nextInt(12);
|
||||
if(randomiser < 5 && !spells.isEmpty()){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){
|
||||
// This means it is more likely for spell books sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
|
||||
}
|
||||
}else if(randomiser < 6){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard has an element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
|
||||
}
|
||||
}else if(randomiser < 8){
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 2);
|
||||
}else{
|
||||
List<Item> upgrades = new ArrayList<Item>(WandHelper.getSpecialUpgrades());
|
||||
randomiser = rand.nextInt(upgrades.size());
|
||||
return new ItemStack(upgrades.get(randomiser));
|
||||
}
|
||||
|
||||
case MASTER:
|
||||
// If a regular wizard rolls a master trade, it can only be a simple master wand or a tome of arcana
|
||||
randomiser = this.getElement() != Element.MAGIC ? rand.nextInt(8) : 5 + rand.nextInt(3);
|
||||
|
||||
if(randomiser < 5 && this.getElement() != Element.MAGIC && !specialismSpells.isEmpty()){
|
||||
// Master spells can only be sold by a specialist in that element.
|
||||
return new ItemStack(WizardryItems.spell_book, 1, specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
|
||||
|
||||
}else if(randomiser < 6){
|
||||
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
|
||||
// Master elemental wands can only be sold by a specialist in that element.
|
||||
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.master_wand);
|
||||
}
|
||||
}else{
|
||||
return new ItemStack(WizardryItems.arcane_tome, 1, 3);
|
||||
}
|
||||
}
|
||||
|
||||
return new ItemStack(Blocks.STONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProfession(VillagerProfession prof) {
|
||||
// Disables Forge's stuff.
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata){
|
||||
|
||||
livingdata = super.onInitialSpawn(difficulty, livingdata);
|
||||
|
||||
textureIndex = this.rand.nextInt(6);
|
||||
|
||||
if(rand.nextBoolean()){
|
||||
this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]);
|
||||
}else{
|
||||
this.setElement(Element.MAGIC);
|
||||
}
|
||||
|
||||
Element element = this.getElement();
|
||||
|
||||
// Adds armour.
|
||||
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
|
||||
this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot)));
|
||||
}
|
||||
|
||||
// Default chance is 0.085f, for reference.
|
||||
for(EntityEquipmentSlot slot : EntityEquipmentSlot.values()) this.setDropChance(slot, 0.0f);
|
||||
|
||||
// All wizards know magic missile, even if it is disabled.
|
||||
spells.add(Spells.magic_missile);
|
||||
|
||||
Tier maxTier = populateSpells(spells, element, 3, rand);
|
||||
|
||||
// Now done after the spells so it can take the tier into account.
|
||||
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryUtilities.getWand(maxTier, element)));
|
||||
|
||||
return livingdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds n random spells to the given list. The spells will be of the given element if possible. Extracted as a
|
||||
* separate function since it was the same in both EntityWizard and EntityEvilWizard.
|
||||
* @param spells The spell list to be populated.
|
||||
* @param e The element that the spells should belong to, or {@link Element#MAGIC} for a random element each time.
|
||||
* @param n The number of spells to add.
|
||||
* @param random A random number generator to use.
|
||||
* @return The tier of the highest-tier spell that was added to the list.
|
||||
*/
|
||||
static Tier populateSpells(List<Spell> spells, Element e, int n, Random random){
|
||||
|
||||
// This is the tier of the highest tier spell added.
|
||||
Tier maxTier = Tier.BASIC;
|
||||
|
||||
List<Spell> npcSpells = Spell.getSpells(Spell.npcSpells);
|
||||
|
||||
for(int i=0; i<3; i++){
|
||||
|
||||
Tier tier;
|
||||
// If the wizard has no element, it picks a random one each time.
|
||||
Element element = e == Element.MAGIC ? Element.values()[random.nextInt(Element.values().length)] : e;
|
||||
|
||||
int randomiser = random.nextInt(20);
|
||||
|
||||
// Uses its own special weighting
|
||||
if(randomiser < 10){
|
||||
tier = Tier.BASIC;
|
||||
}else if(randomiser < 16){
|
||||
tier = Tier.APPRENTICE;
|
||||
}else{
|
||||
tier = Tier.ADVANCED;
|
||||
}
|
||||
|
||||
if(tier.ordinal() > maxTier.ordinal()) maxTier = tier;
|
||||
|
||||
// Finds all the spells of the chosen tier and element
|
||||
List<Spell> list = Spell.getSpells(new Spell.TierElementFilter(tier, element));
|
||||
// Keeps only spells which can be cast by NPCs
|
||||
list.retainAll(npcSpells);
|
||||
// Removes spells that the wizard already has
|
||||
list.removeAll(spells);
|
||||
|
||||
// Ensures the tier chosen actually has spells in it. (isEmpty() is exactly the same as size() == 0)
|
||||
if(list.isEmpty()){
|
||||
// If there are no spells applicable, tier and element restrictions are removed to give maximum
|
||||
// possibility of there being an applicable spell.
|
||||
list = npcSpells;
|
||||
// Removes spells that the wizard already has
|
||||
list.removeAll(spells);
|
||||
}
|
||||
|
||||
// If the list is still empty now, there must be less than 3 enabled spells that can be cast by wizards
|
||||
// (excluding magic missile). In this case, having empty slots seems reasonable.
|
||||
if(!list.isEmpty()) spells.add(list.get(random.nextInt(list.size())));
|
||||
|
||||
}
|
||||
|
||||
return maxTier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(ByteBuf data){
|
||||
data.writeInt(textureIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSpawnData(ByteBuf data){
|
||||
textureIndex = data.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attackEntityFrom(DamageSource source, float damage){
|
||||
|
||||
if(source.getEntity() instanceof EntityPlayer){
|
||||
((EntityPlayer)source.getEntity()).addStat(WizardryAchievements.anger_wizard);
|
||||
}
|
||||
|
||||
return super.attackEntityFrom(source, damage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of blocks that are part of this wizard's tower. If a player breaks any of these blocks, the wizard
|
||||
* will get angry and attack them.
|
||||
* @param blocks A Set of BlockPos objects representing the blocks in the tower.
|
||||
*/
|
||||
public void setTowerBlocks(Set<BlockPos> blocks){
|
||||
this.towerBlocks = blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the block at the given coordinates is part of this wizard's tower.
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @return
|
||||
*/
|
||||
public boolean isBlockPartOfTower(BlockPos pos){
|
||||
if(this.towerBlocks == null) return false;
|
||||
// Uses .equals() rather than == so this will work fine.
|
||||
return this.towerBlocks.contains(pos);
|
||||
}
|
||||
|
||||
// EntityVillager overrides (that don't add features)
|
||||
|
||||
@Override public boolean isMating(){ return false; }
|
||||
@Override public void setMating(boolean p_70947_1_){}
|
||||
@Override public void setPlaying(boolean p_70939_1_){}
|
||||
@Override public boolean isPlaying(){ return false; }
|
||||
@Override public void setLookingForHome(){}
|
||||
// Doesn't say it, but this is in fact nullable.
|
||||
@Override public EntityVillager createChild(EntityAgeable par1EntityAgeable){ return null; }
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override public void setRecipes(MerchantRecipeList par1MerchantRecipeList){}
|
||||
@Override
|
||||
public void onStruckByLightning(EntityLightningBolt lightningBolt){
|
||||
// Restores the normal behaviour, replacing EntityVillager's witch conversion.
|
||||
this.attackEntityFrom(DamageSource.lightningBolt, 5.0F);
|
||||
// Entity's version does something strange with the private fire variable, but since I don't have access this
|
||||
// will probably be fine.
|
||||
this.setFire(8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import net.minecraft.entity.EntityFlying;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIHurtByTarget;
|
||||
import net.minecraft.entity.ai.EntityAIMoveThroughVillage;
|
||||
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
|
||||
import net.minecraft.entity.monster.EntityZombie;
|
||||
import net.minecraft.entity.monster.ZombieType;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumParticleTypes;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class EntityZombieMinion extends EntityZombie implements ISummonedCreature {
|
||||
|
||||
// Field implementations
|
||||
private int lifetime = 600;
|
||||
private WeakReference<EntityLivingBase> casterReference;
|
||||
private UUID casterUUID;
|
||||
|
||||
// Setter + getter implementations
|
||||
@Override public int getLifetime(){ return lifetime; }
|
||||
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
|
||||
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
|
||||
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
|
||||
@Override public UUID getCasterUUID() { return casterUUID; }
|
||||
@Override public void setCasterUUID(UUID uuid) { this.casterUUID = uuid; }
|
||||
|
||||
/**
|
||||
* Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't
|
||||
* matter because the client side entity immediately gets the lifetime value copied over to it by this class
|
||||
* anyway. When extending this class, you must override this constructor or Minecraft won't like it, but there's
|
||||
* no need to do anything inside it other than call super().
|
||||
*/
|
||||
public EntityZombieMinion(World world){
|
||||
super(world);
|
||||
this.experienceValue = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when
|
||||
* extending this class (be sure to call super()) so that AI and other things can be added.
|
||||
*/
|
||||
public EntityZombieMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){
|
||||
super(world);
|
||||
this.setPosition(x, y, z);
|
||||
this.casterReference = new WeakReference<EntityLivingBase>(caster);
|
||||
this.experienceValue = 0;
|
||||
this.lifetime = lifetime;
|
||||
}
|
||||
|
||||
// EntityZombie overrides (EntityZombie is a long class so there are lots of these)
|
||||
|
||||
@Override
|
||||
protected void applyEntityAI()
|
||||
{
|
||||
this.tasks.addTask(6, new EntityAIMoveThroughVillage(this, 1.0D, false));
|
||||
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
|
||||
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
|
||||
0, false, true, this.getTargetSelector()));
|
||||
}
|
||||
|
||||
@Override public boolean isChild(){ return false; }
|
||||
@Override public void setChild(boolean childZombie){}
|
||||
@Override public ZombieType getZombieType(){ return ZombieType.NORMAL; }
|
||||
@Override public boolean isVillager(){ return false; }
|
||||
@Override public net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession getVillagerTypeForge(){ return null; }
|
||||
@Override protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty){} // They don't have equipment!
|
||||
@Override public void onKillEntity(EntityLivingBase entityLivingIn){} // Turns villagers to zombies in EntityZombie
|
||||
@Override protected void startConversion(int ticks){}
|
||||
@Override public boolean isConverting(){ return false; }
|
||||
@Override protected void convertToVillager(){}
|
||||
@Override protected int getConversionTimeBoost(){ return 0; }
|
||||
@Override public void setChildSize(boolean isChild){}
|
||||
|
||||
// Implementations
|
||||
|
||||
@Override
|
||||
public void setRevengeTarget(EntityLivingBase entity){
|
||||
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(){
|
||||
super.onUpdate();
|
||||
this.updateDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDespawn(){
|
||||
this.spawnParticleEffect();
|
||||
}
|
||||
|
||||
private void spawnParticleEffect(){
|
||||
if(this.worldObj.isRemote){
|
||||
for(int i=0;i<15;i++){
|
||||
this.worldObj.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat(), this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParticleEffect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
// In this case, the delegate method determines whether super is called.
|
||||
// Rather handily, we can make use of Java's 'stop as soon as you find true' method of evaluating OR statements.
|
||||
return this.interactDelegate(player, hand, stack) || super.processInteract(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
|
||||
super.writeEntityToNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
|
||||
super.readEntityFromNBT(nbttagcompound);
|
||||
this.writeNBTDelegate(nbttagcompound);
|
||||
}
|
||||
|
||||
// Recommended overrides
|
||||
|
||||
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
|
||||
@Override protected boolean canDropLoot(){ return false; }
|
||||
@Override protected Item getDropItem(){ return null; }
|
||||
@Override protected ResourceLocation getLootTable(){ return null; }
|
||||
@Override public boolean canPickUpLoot(){ return false; }
|
||||
// This vanilla method has nothing to do with the custom despawn() method.
|
||||
@Override protected boolean canDespawn(){ return false; }
|
||||
|
||||
@Override
|
||||
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
|
||||
// Returns true unless the given entity type is a flying entity.
|
||||
return !EntityFlying.class.isAssignableFrom(entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDisplayName(){
|
||||
if(getCaster() != null){
|
||||
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
|
||||
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
|
||||
}else{
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomName(){
|
||||
// If this returns true, the renderer will show the nameplate when looking directly at the entity
|
||||
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
|
||||
/** [NYI] Interface for entities that can select between spells based on their current circumstances, to be used in
|
||||
* conjunction with {@link EntityAISelectSpell}. */
|
||||
public interface IIntelligentSpellCaster extends ISpellCaster {
|
||||
|
||||
/** Called from {@link EntityAISelectSpell} to set the spells that the entity can use in its current situation.
|
||||
* Implementors should assign the given list to some internal field and retrieve it when {@link #getSpells()} is
|
||||
* called. */
|
||||
public void setCurrentSpells(List<Spell> spells);
|
||||
|
||||
/** Returns a list of spells that this entity 'knows'. The entity's current spell will be selected from this list
|
||||
* based on the current situation: whether it is attacking, its health, etc. Most likely, you will want to return a
|
||||
* constant list, but it may change for some reason - perhaps if the entity 'learns' a new spell. */
|
||||
public List<Spell> getKnownSpells();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
|
||||
/** Interface for entities that can cast spells. Mainly intended for use by wizard-type entities, but can be
|
||||
* implemented by any subclass of EntityLiving. Designed to be as flexible as possible - ranging from the simplest
|
||||
* use of giving an entity a specific spell as an attack, to a complex AI which selects different spell types
|
||||
* depending on the situation. The only restriction is that the spells must be castable by NPCs.
|
||||
* <p>
|
||||
* This is intended for entities that use {@link EntityAIAttackSpell}. If so, all the spell casting code (including
|
||||
* packets) is handled by that class, and all the implementor needs to do is decide which spell(s) to select.
|
||||
* <p>
|
||||
* This class also allows Wizardry to do all the syncing necessary for continuous spell casting. All the implementor
|
||||
* needs to do is store the actual fields involved.
|
||||
*/
|
||||
/* Perhaps this should be a capability? Though I can't help thinking they're mainly for attaching data to vanilla
|
||||
* classes, rather than custom ones. For now, the main purpose of this is to centralise code within wizardry itself,
|
||||
* and I may as well make it an API feature - but I'm not writing a capability unless someone sees a reason to attach
|
||||
* it to a class they don't own, and I don't see that happening anytime soon. */
|
||||
// For even more fun, combine this with an ISummonedCreature and have skeletons that can cast spells!
|
||||
public interface ISpellCaster {
|
||||
|
||||
/**
|
||||
* Called each time the entity attacks to get the spells that can be cast. For simple implementations, just
|
||||
* return a list of size one containing the spell that the entity uses as an attack, perhaps performing some
|
||||
* simple logic within this method. For more intelligent implementations based on spell types, consider using
|
||||
* {@link IIntelligentSpellCaster} instead.
|
||||
* @return A list of {@link Spell} instances. A random spell from this list will be cast when the entity attacks.
|
||||
* The list will not be modified by the AI class and can therefore be an immutable list. The spells in the list
|
||||
* <b>must</b> be castable by NPCs (i.e. {@link Spell#canBeCastByNPCs()} returns true).
|
||||
*/
|
||||
@Nonnull
|
||||
public List<Spell> getSpells();
|
||||
|
||||
/**
|
||||
* Called each time the entity attacks to get the modifiers to apply to the spell.
|
||||
* @return A {@link SpellModifiers} object representing the modifiers to apply to the spell. If no modifiers are required,
|
||||
* pass in an empty {@code SpellModifiers} object.
|
||||
*/
|
||||
@Nonnull
|
||||
public SpellModifiers getModifiers();
|
||||
|
||||
/** Returns the continuous spell that is currently being cast, or the None spell if there is none. Implementors should simply
|
||||
* store this as a private field and return it here. Will be synced by the AI class, but whether it is saved to NBT
|
||||
* is up to you. If the implementing class does not deal with continuous spells, just return {@link Spells#none}. If the
|
||||
* implementing class only ever uses one continuous spell, do <b>not</b> just return that spell; the field must still
|
||||
* be stored. */
|
||||
@Nonnull
|
||||
public Spell getContinuousSpell();
|
||||
|
||||
/** Sets the continuous spell that is currently being cast, or the None spell if there is none. Implementors should simply
|
||||
* store this as a private field and assign it here. Will be synced by the AI class, but whether it is saved to NBT
|
||||
* is up to you. If the implementing class does not deal with continuous spells, leave this method blank. */
|
||||
public void setContinuousSpell(Spell spell);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package electroblob.wizardry.entity.living;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.WizardryEventHandler;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.monster.IMob;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
/** Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 1.2, and this
|
||||
* interface allows summoned creatures to extend vanilla (or indeed modded) entity classes, so <code>EntitySummonedZombie</code>
|
||||
* now extends <code>EntityZombie</code>, for example. This change has two major benefits:
|
||||
* <p>
|
||||
* - There is no longer any need for separate render classes, because summoned creatures are now instances of vanilla
|
||||
* types. <i>You don't even need to assign a render class</i> because the supertype should already be assigned the correct one.<br>
|
||||
* - Summoned creature classes are now much more robust when it comes to changes between Minecraft versions, since none
|
||||
* of the vanilla code needs to be copied.
|
||||
* <p>
|
||||
* <b>Summoned creatures that do not emulate vanilla entities do not directly implement this interface</b>. Instead, they
|
||||
* should extend the abstract base implementation, {@link EntitySummonedCreature}.
|
||||
* <p>
|
||||
* All damage dealt by ISummonedCreature instances is redirected via {@link WizardryEventHandler#onLivingAttackEvent(
|
||||
* net.minecraftforge.event.entity.living.LivingAttackEvent) WizardryEventHandler.onLivingAttackEvent(LivingAttackEvent)}
|
||||
* and replaced by an instance of {@link electroblob.wizardry.util.IElementalDamage IElementalDamage} with the summoner
|
||||
* of that creature as the source rather than the creature itself. This means that kills by summoned creatures register
|
||||
* as player kills, dropping xp and rare loot.
|
||||
* <p>
|
||||
* Though this system is a lot better than the previous system, <i>it is not a perfect solution</i>. The old
|
||||
* EntitySummonedCreature class overrode some methods from Entity in order to add shared functionality, but this cannot
|
||||
* be done with an interface. To get around this problem, this interface contains 5 delegate methods that do the same
|
||||
* things, with the aim of centralising as much code as possible, even though it is not automatically applied.
|
||||
* <b>Implementing classes must override the corresponding methods from Entity, and within them, call the appropriate
|
||||
* delegate method in this interface.</b> It is impossible to enforce this condition, but the summoned creature will not
|
||||
* work properly unless it is adhered to. <i>The position of the delegate method call is unimportant, but by convention
|
||||
* it is usually at the start of the calling method, which avoids it being unintentionally skipped by a return statement.</i>
|
||||
* <p>
|
||||
* It is recommended that when implementing this interface, you begin by copying {@link EntitySummonedCreature} to ensure
|
||||
* all the relevant methods are duplicated. You can then change the superclass, override any additional methods and add
|
||||
* functionality to any that are already overridden. You will always want to override the AI methods at the very least.
|
||||
* <p>
|
||||
* Due to the limitations of interfaces, some methods that really ought to be protected are public. These are clearly
|
||||
* marked as 'Internal, DO NOT CALL'. <b>Don't call them, only implement them.</b>
|
||||
* @since Wizardry 1.2
|
||||
* @author Electroblob */
|
||||
/*
|
||||
* Quite honestly, this is not what default methods are really for. However, this is modding, and in modding some
|
||||
* sacrifices have to be made when it comes to Java style - because adding on to a pre-existing program is not a
|
||||
* good way of doing this sort of thing anyway, but we have no choice about that!
|
||||
*/
|
||||
public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
|
||||
// Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that.
|
||||
String NAMEPLATE_TRANSLATION_KEY = "entity.wizardry.summonedcreature.nameplate";
|
||||
|
||||
// Setters and getters. The subclass fields that these access should be private.
|
||||
|
||||
/** Sets the lifetime of the summoned creature in ticks. */
|
||||
void setLifetime(int ticks);
|
||||
|
||||
/** Returns the lifetime of the summoned creature in ticks. Allows primarily for duration multiplier support, but
|
||||
* also for example the skeleton legion spell which lasts for 60 seconds instead of the usual 30. Syncing and saving
|
||||
* is done automatically. As of Wizardry 1.2, despawning is handled in ISummonedCreature; see
|
||||
* {@link ISummonedCreature#onDespawn()} for details. */
|
||||
int getLifetime();
|
||||
|
||||
/** Sets the WeakReference object which refers to the owner of this summoned creature. Internal, don't call
|
||||
* unless you know what you are doing. */
|
||||
void setCasterReference(WeakReference<EntityLivingBase> reference);
|
||||
|
||||
/** Returns a WeakReference object which refers to the owner of this summoned creature. Subclasses should store this
|
||||
* as a private field. This may be null; as such it is preferable to use {@link ISummonedCreature#getCaster()} to get
|
||||
* the caster object itself. */
|
||||
@Nullable
|
||||
WeakReference<EntityLivingBase> getCasterReference();
|
||||
|
||||
/** Internal, DO NOT CALL. */
|
||||
void setCasterUUID(UUID uuid);
|
||||
/** Internal, DO NOT CALL. This is for loading purposes only and is not usually synchronised. */
|
||||
UUID getCasterUUID();
|
||||
|
||||
/** Returns the EntityLivingBase that summoned this creature, or null if it no longer exists. Cases where the
|
||||
* entity may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported
|
||||
* to another dimension, or this creature simply had no caster in the first place. <i>This is the correct method
|
||||
* to use to get the owner of this summoned creature. */
|
||||
@Nullable
|
||||
default EntityLivingBase getCaster(){
|
||||
return getCasterReference() == null ? null : getCasterReference().get();
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
/**
|
||||
* Called by the server when constructing the spawn packet.
|
||||
* Data should be added to the provided stream.
|
||||
* <b>Implementors must call super when overriding.</b>
|
||||
*
|
||||
* @param buffer The packet data stream
|
||||
*/
|
||||
@Override
|
||||
default void writeSpawnData(ByteBuf buffer) {
|
||||
buffer.writeInt(getCaster() != null ? getCaster().getEntityId() : -1);
|
||||
buffer.writeInt(getLifetime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the client when it receives a Entity spawn packet.
|
||||
* Data should be read out of the stream in the same way as it was written.
|
||||
* <b>Implementors must call super when overriding.</b>
|
||||
*
|
||||
* @param additionalData The packet data stream
|
||||
*/
|
||||
@Override
|
||||
default void readSpawnData(ByteBuf buffer) {
|
||||
int id = buffer.readInt();
|
||||
// We're on the client side here, so we can safely use Minecraft.getMinecraft().theWorld via proxies.
|
||||
if(id > -1) setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)Wizardry.proxy.getTheWorld().getEntityByID(id)));
|
||||
setLifetime(buffer.readInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this creature as the
|
||||
* attacker. Also allows implementors to override it if they wish to do so.
|
||||
*/
|
||||
default boolean isValidTarget(Entity target){
|
||||
return WizardryUtilities.isValidTarget(this.getCaster(), target);
|
||||
}
|
||||
|
||||
/** Returns a entity selector to be passed into AI methods. Normally, this should not be overridden, but it is
|
||||
* possible for implementors to override this in order to do something special when selecting a target. */
|
||||
default Predicate<Entity> getTargetSelector(){
|
||||
|
||||
return new Predicate<Entity>(){
|
||||
|
||||
public boolean apply(Entity entity){
|
||||
// TODO: Backport invisibility check (also in wizards)
|
||||
// If the target is valid and not invisible...
|
||||
if(!entity.isInvisible() && isValidTarget(entity)){
|
||||
|
||||
//... and is a player, they can be attacked, since players can't be in the whitelist or the blacklist.
|
||||
if(entity instanceof EntityPlayer) return true;
|
||||
|
||||
//... and is a mob, a summoned creature, a wizard ...
|
||||
if((entity instanceof IMob || entity instanceof ISummonedCreature || (entity instanceof EntityWizard && !(getCaster() instanceof EntityWizard))
|
||||
// ... or in the whitelist ...
|
||||
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT)))
|
||||
// ... and isn't in the blacklist ...
|
||||
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist).contains(EntityList.getEntityString(entity).toLowerCase(Locale.ROOT))){
|
||||
// ... it can be attacked.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this creature has existed for 1 tick, effectively when it has just been spawned. Normally used
|
||||
* to add particles, sounds, etc.
|
||||
*/
|
||||
void onSpawn();
|
||||
|
||||
/**
|
||||
* Called when this summoned creature vanishes. Normally used to add particles, sounds, etc.
|
||||
*/
|
||||
void onDespawn();
|
||||
|
||||
/** Whether this creature should spawn a subtle black swirl particle effect while alive. */
|
||||
boolean hasParticleEffect();
|
||||
|
||||
/** Called from the event handler after the damage change is applied. Does nothing by default, but can be overridden
|
||||
* to do something when a successful attack is made. This was added because the event-based damage source system
|
||||
* can cause parts of attackEntityAsMob not to fire, since attackEntityFrom is intercepted and canceled.
|
||||
* <p>
|
||||
* Usage examples: {@link EntitySliverfishMinion} uses this to summon more silverfish if the target is killed,
|
||||
* {@link EntitySkeletonMinion} and {@link EntitySpiderMinion} use this to add potion effects to the target. */
|
||||
default void onSuccessfulAttack(EntityLivingBase target){};
|
||||
|
||||
// Delegates
|
||||
|
||||
/** Implementors should call this from writeEntityToNBT. Can be overridden as long as super is called, but there's
|
||||
* very little point in doing that since anything extra could just be added to writeEntityToNBT anyway. */
|
||||
default void writeNBTDelegate(NBTTagCompound tagcompound){
|
||||
if(this.getCaster() != null){
|
||||
tagcompound.setUniqueId("casterUUID", this.getCaster().getUniqueID());
|
||||
}
|
||||
tagcompound.setInteger("lifetime", getLifetime());
|
||||
}
|
||||
|
||||
/** Implementors should call this from readEntityFromNBT. Can be overridden as long as super is called, but there's
|
||||
* very little point in doing that since anything extra could just be added to readEntityFromNBT anyway. */
|
||||
default void readNBTDelegate(NBTTagCompound tagcompound){
|
||||
this.setCasterUUID(tagcompound.getUniqueId("casterUUID"));
|
||||
this.setLifetime(tagcompound.getInteger("lifetime"));
|
||||
}
|
||||
|
||||
/** Implementors should call this from setRevengeTarget, and call super.setRevengeTarget if and only if this method
|
||||
* returns <b>true</b>. */
|
||||
default boolean shouldRevengeTarget(EntityLivingBase entity){
|
||||
// Allows the config to prevent minions from revenge-targeting their owners.
|
||||
return entity != this.getCaster() || Wizardry.settings.minionRevengeTargeting;
|
||||
}
|
||||
|
||||
/** Implementors should call this from onUpdate. Can be overridden as long as super is called, but there's
|
||||
* very little point in doing that since anything extra could just be added to onUpdate anyway. */
|
||||
default void updateDelegate(){
|
||||
|
||||
if(!(this instanceof Entity)) throw new ClassCastException("Implementations of ISummonedCreature must extend Entity!");
|
||||
|
||||
Entity thisEntity = ((Entity)this);
|
||||
|
||||
if(this.getCaster() == null && this.getCasterUUID() != null){
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(thisEntity.worldObj, getCasterUUID());
|
||||
if(entity instanceof EntityLivingBase){
|
||||
this.setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)entity));
|
||||
}
|
||||
}
|
||||
|
||||
if(thisEntity.ticksExisted == 1){
|
||||
this.onSpawn();
|
||||
}
|
||||
|
||||
if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() != -1){
|
||||
this.onDespawn();
|
||||
thisEntity.setDead();
|
||||
}
|
||||
|
||||
if(this.hasParticleEffect() && thisEntity.worldObj.isRemote && thisEntity.worldObj.rand.nextInt(8) == 0)
|
||||
Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, thisEntity.worldObj, thisEntity.posX, thisEntity.posY + thisEntity.worldObj.rand.nextDouble()*1.5, thisEntity.posZ, 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.0f, 0.0f);
|
||||
|
||||
}
|
||||
|
||||
/** Implementors should call this from processInteract, and call super.processInteract if and only if this method
|
||||
* returns <b>false</b>. */
|
||||
default boolean interactDelegate(EntityPlayer player, EnumHand hand, ItemStack stack) {
|
||||
|
||||
WizardData properties = WizardData.get(player);
|
||||
// Selects one of the player's minions.
|
||||
if(player.isSneaking() && stack != null && stack.getItem() instanceof ItemWand){
|
||||
|
||||
if(!player.worldObj.isRemote && properties != null && this.getCaster() == player){
|
||||
|
||||
if(properties.selectedMinion != null && properties.selectedMinion.get() == this){
|
||||
// Deselects the selected minion if right-clicked again
|
||||
properties.selectedMinion = null;
|
||||
}else{
|
||||
// Selects this minion
|
||||
properties.selectedMinion = new WeakReference<ISummonedCreature>(this);
|
||||
}
|
||||
properties.sync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user