Initial commit

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