That's one heck of a commit you've got there...

I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
Electroblob77
2019-08-18 00:04:18 +01:00
parent 2680d02304
commit f37812be3e
1564 changed files with 47652 additions and 12984 deletions
@@ -1,8 +1,5 @@
package electroblob.wizardry.entity.living;
import java.util.ArrayList;
import java.util.List;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.packet.PacketNPCCastSpell;
@@ -18,6 +15,9 @@ import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Entity AI class for use by instances of {@link ISpellCaster}. This deals with pathing, the spell casting itself and
* the attack cooldown. Also provides an automatic implementation of continuous spell casting using the methods
@@ -106,7 +106,7 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
attacker.setContinuousSpell(spell);
WizardryPacketHandler.net.sendToAllAround(
new PacketNPCCastSpell.Message(attacker.getEntityId(), target == null ? -1 : target.getEntityId(),
EnumHand.MAIN_HAND, spell.id(), modifiers),
EnumHand.MAIN_HAND, spell, modifiers),
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
// TODO: Why is this one a 128 block radius, whilst the other one is all in dimension?
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
@@ -143,8 +143,8 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
// ...or the spell is cancelled via events...
|| MinecraftForge.EVENT_BUS
.post(new SpellCastEvent.Tick(attacker, attacker.getContinuousSpell(), attacker.getModifiers(),
Source.NPC, this.continuousSpellDuration - this.continuousSpellTimer))
.post(new SpellCastEvent.Tick(Source.NPC, attacker.getContinuousSpell(), attacker,
attacker.getModifiers(), this.continuousSpellDuration - this.continuousSpellTimer))
// ...or the spell no longer succeeds...
|| !attacker.getContinuousSpell().cast(attacker.world, attacker, EnumHand.MAIN_HAND,
this.continuousSpellDuration - this.continuousSpellTimer, target, attacker.getModifiers())
@@ -159,8 +159,8 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
}else if(this.continuousSpellDuration - this.continuousSpellTimer == 1){
// On the first tick, if the spell did succeed, fire SpellCastEvent.Post.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, attacker.getContinuousSpell(),
attacker.getModifiers(), Source.NPC));
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, attacker.getContinuousSpell(),
attacker, attacker.getModifiers()));
}
}else if(--this.cooldown == 0){
@@ -209,7 +209,7 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
private boolean attemptCastSpell(Spell spell, SpellModifiers modifiers){
// If anything stops the spell working at this point, nothing else happens.
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(attacker, spell, modifiers, Source.NPC))){
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.NPC, spell, attacker, modifiers))){
return false;
}
@@ -222,16 +222,16 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
}else{
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, spell, modifiers, Source.NPC));
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, spell, attacker, modifiers));
// For now, the cooldown is just added to the constant base cooldown. I think this
// is a reasonable way of doing things; it's certainly better than before.
this.cooldown = this.baseCooldown + spell.cooldown;
this.cooldown = this.baseCooldown + spell.getCooldown();
if(spell.doesSpellRequirePacket()){
if(spell.requiresPacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(), target.getEntityId(),
EnumHand.MAIN_HAND, spell.id(), modifiers);
EnumHand.MAIN_HAND, spell, modifiers);
WizardryPacketHandler.net.sendToDimension(msg, attacker.world.provider.getDimension());
}
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.UUID;
import electroblob.wizardry.Wizardry;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
@@ -16,13 +13,15 @@ import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import java.util.UUID;
public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature {
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@@ -37,22 +36,12 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
}
@Override
public WeakReference<EntityLivingBase> getCasterReference(){
return casterReference;
}
@Override
public void setCasterReference(WeakReference<EntityLivingBase> reference){
casterReference = reference;
}
@Override
public UUID getCasterUUID(){
public UUID getOwnerId(){
return casterUUID;
}
@Override
public void setCasterUUID(UUID uuid){
public void setOwnerId(UUID uuid){
this.casterUUID = uuid;
}
@@ -65,13 +54,13 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
// EntityBlaze overrides
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
// targeting system with one that targets hostile mobs and takes the ADS into account.
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account.
@Override
protected void initEntityAI(){
super.initEntityAI();
this.targetTasks.taskEntries.clear();
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
0, false, true, this.getTargetSelector()));
}
@@ -142,8 +131,16 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -163,6 +160,6 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,10 +1,7 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAILookIdle;
@@ -99,18 +96,19 @@ public class EntityDecoy extends EntitySummonedCreature {
return false;
}
@Override
public void writeSpawnData(ByteBuf data){
super.writeSpawnData(data);
if(this.getCaster() != null) data.writeInt(this.getCaster().getEntityId());
}
@Override
public void readSpawnData(ByteBuf data){
super.readSpawnData(data);
if(!data.isReadable()) return;
this.setCasterReference(
new WeakReference<EntityLivingBase>((EntityLivingBase)this.world.getEntityByID(data.readInt())));
}
// TESTME: Why was this here? It gets done in ISummonedCreature anyway
// @Override
// public void writeSpawnData(ByteBuf data){
// super.writeSpawnData(data);
// if(this.getCaster() != null) data.writeInt(this.getCaster().getEntityId());
// }
//
// @Override
// public void readSpawnData(ByteBuf data){
// super.readSpawnData(data);
// if(!data.isReadable()) return;
// this.setCasterReference(
// new WeakReference<EntityLivingBase>((EntityLivingBase)this.world.getEntityByID(data.readInt())));
// }
}
@@ -1,11 +1,6 @@
package electroblob.wizardry.entity.living;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.base.Predicate;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
@@ -15,31 +10,18 @@ import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityLivingData;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAIOpenDoor;
import net.minecraft.entity.ai.EntityAIRestrictOpenDoor;
import net.minecraft.entity.ai.EntityAISwimming;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest2;
import net.minecraft.entity.*;
import net.minecraft.entity.ai.*;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagInt;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
@@ -55,13 +37,21 @@ import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import javax.annotation.Nullable;
import java.util.*;
public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntityAdditionalSpawnData {
private EntityAIAttackSpell<EntityEvilWizard> spellCastingAI = new EntityAIAttackSpell<EntityEvilWizard>(this, 0.5D, 14.0F, 30, 50);
private EntityAIAttackSpell<EntityEvilWizard> spellCastingAI = new EntityAIAttackSpell<>(this, 0.5D, 14.0F, 30, 50);
public int textureIndex = 0;
public boolean hasTower = false;
/** True if this evil wizard was spawned as part of a structure (tower or shrine), false if it spawned naturally. */
public boolean hasStructure = false;
/** Stores the UUIDs of the other evil wizards spawned in the same group, if any. The wizard will not revenge-target
* entities whose UUIDs are in this set. This is currently used only for shrines. */
public final Set<UUID> groupUUIDs = new HashSet<>();
/** The entity selector passed into the new AI methods. */
protected Predicate<Entity> targetSelector;
@@ -95,11 +85,12 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
protected void entityInit(){
super.entityInit();
this.dataManager.register(HEAL_COOLDOWN, -1);
this.dataManager.register(ELEMENT, 0);
this.dataManager.register(ELEMENT, -1);
}
@Override
protected void initEntityAI(){
this.tasks.addTask(0, new EntityAISwimming(this));
this.tasks.addTask(4, new EntityAIRestrictOpenDoor(this));
this.tasks.addTask(5, new EntityAIOpenDoor(this, true));
@@ -107,34 +98,31 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
this.tasks.addTask(7, new EntityAIWander(this, 0.6D));
this.targetSelector = new Predicate<Entity>(){
this.targetSelector = entity -> {
public boolean apply(Entity entity){
// If the target is valid and not invisible...
if(entity != null && !entity.isInvisible()
&& AllyDesignationSystem.isValidTarget(EntityEvilWizard.this, entity)){
// If the target is valid and not invisible...
if(entity != null && !entity.isInvisible()
&& WizardryUtilities.isValidTarget(EntityEvilWizard.this, entity)){
// ... and is a player, a summoned creature, another (non-evil) wizard ...
if(entity instanceof EntityPlayer
|| (entity instanceof ISummonedCreature || entity instanceof EntityWizard
// ... or in the whitelist ...
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist)
.contains(EntityList.getKey(entity.getClass())))
// ... and isn't in the blacklist ...
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
.contains(EntityList.getKey(entity.getClass()))){
// ... it can be attacked.
return true;
}
// ... and is a player, a summoned creature, another (non-evil) wizard ...
if(entity instanceof EntityPlayer
|| (entity instanceof ISummonedCreature || entity instanceof EntityWizard
// ... or in the whitelist ...
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist)
.contains(EntityList.getKey(entity.getClass())))
// ... and isn't in the blacklist ...
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
.contains(EntityList.getKey(entity.getClass()))){
// ... it can be attacked.
return true;
}
return false;
}
return false;
};
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true));
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
0, false, true, this.targetSelector));
}
@@ -154,7 +142,8 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
}
public Element getElement(){
return Element.values()[this.dataManager.get(ELEMENT)];
int n = this.dataManager.get(ELEMENT);
return n == -1 ? null : Element.values()[n];
}
public void setElement(Element element){
@@ -192,6 +181,11 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
}
}
@Override
public void setRevengeTarget(@Nullable EntityLivingBase target){
if(target == null || !groupUUIDs.contains(target.getUniqueID())) super.setRevengeTarget(target);
}
@Override
public void onLivingUpdate(){
@@ -225,13 +219,13 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
}
}else{
if(this.getHealth() < 10){
// Wizards heal themseselves more often if they have low health
// Wizards heal themselves more often if they have low health
this.setHealCooldown(150);
}else{
this.setHealCooldown(400);
}
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
this.playSound(Spells.heal.getSounds()[0], 0.7F, rand.nextFloat() * 0.4F + 1.0F);
}
}
if(healCooldown > 0){
@@ -250,8 +244,8 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// Spell.get(spells[3]).getDisplayName()));
// When right-clicked with a spell book in creative, sets one of the spells to that spell
if(player.capabilities.isCreativeMode && stack.getItem() instanceof ItemSpellBook){
Spell spell = Spell.get(stack.getItemDamage());
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
Spell spell = Spell.byMetadata(stack.getItemDamage());
if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){
// The set(...) method returns the element that was replaced - neat!
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
@@ -269,8 +263,9 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
super.writeEntityToNBT(nbt);
nbt.setInteger("element", this.getElement().ordinal());
nbt.setInteger("skin", this.textureIndex);
nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id())));
nbt.setBoolean("hasTower", this.hasTower);
nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata())));
nbt.setBoolean("hasStructure", this.hasStructure);
nbt.setTag("groupUUIDs", NBTExtras.listToNBT(groupUUIDs, NBTUtil::createUUIDTag));
}
@Override
@@ -278,9 +273,10 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
super.readEntityFromNBT(nbt);
this.setElement(Element.values()[nbt.getInteger("element")]);
this.textureIndex = nbt.getInteger("skin");
this.spells = (List<Spell>)WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
(NBTTagInt tag) -> Spell.get(tag.getInt()));
this.hasTower = nbt.getBoolean("hasTower");
this.spells = (List<Spell>)NBTExtras.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
(NBTTagInt tag) -> Spell.byMetadata(tag.getInt()));
this.hasStructure = nbt.getBoolean("hasStructure");
this.groupUUIDs.addAll(NBTExtras.NBTToList(nbt.getTagList("groupUUIDs", NBT.TAG_COMPOUND), NBTUtil::getUUIDFromTag));
}
@Override
@@ -291,7 +287,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
@Override
public boolean getCanSpawnHere(){
// Evil wizards can only spawn in the specified dimensions
for(int id : Wizardry.settings.evilWizardDimensions){
for(int id : Wizardry.settings.mobSpawnDimensions){
if(this.dimension == id) return super.getCanSpawnHere();
}
@@ -301,27 +297,27 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
@Override
protected boolean canDespawn(){
// Evil wizards can only despawn if they don't have a tower (i.e. if they spawned naturally at night)
return !this.hasTower;
return !this.hasStructure;
}
@Override
protected float getSoundPitch(){
return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F;
}
// @Override
// protected float getSoundPitch(){
// return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F;
// }
@Override
protected SoundEvent getAmbientSound(){
return SoundEvents.ENTITY_WITCH_AMBIENT;
return WizardrySounds.ENTITY_EVIL_WIZARD_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource source){
return SoundEvents.ENTITY_WITCH_HURT;
return WizardrySounds.ENTITY_EVIL_WIZARD_HURT;
}
@Override
protected SoundEvent getDeathSound(){
return SoundEvents.ENTITY_WITCH_DEATH;
return WizardrySounds.ENTITY_EVIL_WIZARD_DEATH;
}
// Although it *looks* like this is still called, in actual fact the only method that calls it is overridden in
@@ -340,7 +336,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// the dropRareDrop method because that would be just as rare as normal mobs; instead this is half as rare.
if(this.spells.size() > 0 && rand.nextInt(100) - lootingLevel < 5)
this.entityDropItem(new ItemStack(WizardryItems.spell_book, 1,
this.spells.get(1 + rand.nextInt(this.spells.size() - 1)).id()), 0);
this.spells.get(1 + rand.nextInt(this.spells.size() - 1)).metadata()), 0);
}
@Override
@@ -355,17 +351,19 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
textureIndex = this.rand.nextInt(6);
if(rand.nextBoolean()){
this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]);
}else{
this.setElement(Element.MAGIC);
if(getElement() == null){
if(rand.nextBoolean()){
this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]);
}else{
this.setElement(Element.MAGIC);
}
}
Element element = this.getElement();
// Adds armour.
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot)));
this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot)));
}
// Default chance is 0.085f, for reference.
@@ -375,12 +373,12 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// All wizards know magic missile, even if it is disabled.
spells.add(Spells.magic_missile);
Tier maxTier = EntityWizard.populateSpells(spells, element, 3, rand);
Tier maxTier = EntityWizard.populateSpells(spells, element, hasStructure, 3, rand);
// Now done after the spells so it can take the tier into account. For evil wizards this is slightly different;
// it picks a random wand which is at least a high enough tier for the spells the wizard has.
Tier tier = Tier.values()[maxTier.ordinal() + rand.nextInt(Tier.values().length - maxTier.ordinal())];
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryUtilities.getWand(tier, element)));
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryItems.getWand(tier, element)));
return data;
}
@@ -0,0 +1,42 @@
package electroblob.wizardry.entity.living;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class EntityHuskMinion extends EntityZombieMinion {
/** Creates a new husk minion in the given world. */
public EntityHuskMinion(World world){
super(world);
}
@Override
protected boolean shouldBurnInDay(){
return false;
}
@Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_HUSK_AMBIENT; }
@Override protected SoundEvent getHurtSound(DamageSource damageSourceIn){ return SoundEvents.ENTITY_HUSK_HURT; }
@Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_HUSK_DEATH; }
@Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_HUSK_STEP; }
@Override
public boolean attackEntityAsMob(Entity target){
boolean flag = super.attackEntityAsMob(target);
if(flag && this.getHeldItemMainhand().isEmpty() && target instanceof EntityLivingBase){
float f = this.world.getDifficultyForLocation(new BlockPos(this)).getAdditionalDifficulty();
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.HUNGER, 140 * (int)f));
}
return flag;
}
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.UUID;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
@@ -10,15 +7,9 @@ import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.EntityFlying;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAIMoveTowardsTarget;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.ai.*;
import net.minecraft.entity.monster.EntityIronGolem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect;
@@ -27,22 +18,22 @@ import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.village.Village;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import java.util.UUID;
public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature {
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
@Override public UUID getCasterUUID(){ return casterUUID; }
@Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
/** Creates a new ice giant in the given world. */
public EntityIceGiant(World world){
@@ -91,7 +82,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
@Override
public void onDespawn(){
this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f);
this.playSound(WizardrySounds.ENTITY_ICE_GIANT_DESPAWN, 1.0f, 1.0f);
this.spawnParticleEffect();
}
@@ -126,7 +117,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
this.applyEnchantments(this, target);
this.playSound(SoundEvents.ENTITY_IRONGOLEM_ATTACK, 1.0F, 1.0F);
this.playSound(WizardrySounds.ENTITY_ICE_GIANT_ATTACK, 1.0F, 1.0F);
}
@Override
@@ -160,8 +151,16 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -182,7 +181,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,16 +1,13 @@
package electroblob.wizardry.entity.living;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.ai.*;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
@@ -66,7 +63,7 @@ public class EntityIceWraith extends EntityBlazeMinion {
}
if(this.rand.nextInt(24) == 0){
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 0.3F + this.rand.nextFloat() / 4,
this.playSound(WizardrySounds.ENTITY_ICE_WRAITH_AMBIENT, 0.3F + this.rand.nextFloat() / 4,
this.rand.nextFloat() * 0.7F + 1.4F);
}
@@ -181,14 +178,24 @@ public class EntityIceWraith extends EntityBlazeMinion {
@Override
public boolean isBurning(){
// Uses the datawatcher on both sides because fire is private to Entity (and I'm not using reflection here).
// TESTME: This should work, but there may be some issues with updating, so if it doesn't work, copy the
// This should work, but there may be some issues with updating, so if it doesn't work, copy the
// version from Entity and use reflection to access the fire field.
return this.getFlag(0);
}
@Override
public boolean getCanSpawnHere(){
// Only spawns in the specified dimensions
for(int id : Wizardry.settings.mobSpawnDimensions){
if(this.dimension == id) return super.getCanSpawnHere() && this.isValidLightLevel();
}
return false;
}
/**
* Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball spawning
* with a one-liner call to WizardryRegistry.iceShard.cast(...) and the removal of redundant local variables.
* with a one-liner call to WizardryLoot.iceShard.cast(...) and the removal of redundant local variables.
*/
static class AIIceShardAttack extends EntityAIBase {
@@ -222,6 +229,7 @@ public class EntityIceWraith extends EntityBlazeMinion {
public void updateTask(){
--this.attackTime;
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
if(entitylivingbase == null) return; // Dynamic stealth breaks things, let's un-break them
double d0 = this.blaze.getDistanceSq(entitylivingbase);
if(d0 < 4.0D){
@@ -251,7 +259,6 @@ public class EntityIceWraith extends EntityBlazeMinion {
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
Spells.ice_shard.cast(this.blaze.world, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase,
new SpellModifiers());
// TODO: Decide if an event should be fired here. I'm guessing no.
}
}
@@ -1,15 +1,12 @@
package electroblob.wizardry.entity.living;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.ai.*;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
@@ -75,9 +72,21 @@ public class EntityLightningWraith extends EntityBlazeMinion {
return this.getFlag(0);
}
@Override
public boolean getCanSpawnHere(){
// Only spawns in the specified dimensions, during thunderstorms
if(!world.isThundering()) return false;
for(int id : Wizardry.settings.mobSpawnDimensions){
if(this.dimension == id) return super.getCanSpawnHere() && this.isValidLightLevel();
}
return false;
}
/**
* Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball spawning
* with a one-liner call to WizardryRegistry.arc.cast(...) and the removal of redundant local variables.
* with a one-liner call to WizardryLoot.arc.cast(...) and the removal of redundant local variables.
*/
static class AILightningAttack extends EntityAIBase {
@@ -111,6 +120,7 @@ public class EntityLightningWraith extends EntityBlazeMinion {
public void updateTask(){
--this.attackTime;
EntityLivingBase entitylivingbase = this.blaze.getAttackTarget();
if(entitylivingbase == null) return; // Dynamic stealth breaks things, let's un-break them
double d0 = this.blaze.getDistanceSq(entitylivingbase);
if(d0 < 4.0D){
@@ -139,7 +149,6 @@ public class EntityLightningWraith extends EntityBlazeMinion {
if(this.attackStep > 1){
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
Spells.arc.cast(this.blaze.world, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
// TODO: Decide if an event should be fired here. I'm guessing no.
}
}
@@ -1,16 +1,11 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.UUID;
import javax.annotation.Nullable;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityLivingData;
import net.minecraft.entity.monster.EntitySlime;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect;
@@ -22,21 +17,21 @@ import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.UUID;
/** As of Wizardry 1.2, this is now an ISummonedCreature like the rest of them, and it extends EntitySlime. */
public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
// Field implementations
private int lifetime = 200;
private WeakReference<EntityLivingBase> casterReference;
private UUID casterUUID;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
@Override public UUID getCasterUUID(){ return casterUUID; }
@Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
public EntityMagicSlime(World world){
super(world);
@@ -57,7 +52,7 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
super(world);
this.setPosition(target.posX, target.posY, target.posZ);
this.startRiding(target);
this.casterReference = new WeakReference<EntityLivingBase>(caster);
this.setOwnerId(caster.getUniqueID());
this.setSlimeSize(2, false); // Needs to be called before setting the experience value to 0
this.experienceValue = 0;
this.lifetime = lifetime;
@@ -83,8 +78,8 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
this.world.spawnParticle(EnumParticleTypes.SLIME, x, y, z, (x - this.posX) * 2, (y - this.posY) * 2,
(z - this.posZ) * 2);
}
this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 2.5f, 0.6f);
this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST_FAR, 1.0f, 0.5f);
this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_SPLAT, 2.5f, 0.6f);
this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_EXPLODE, 1.0f, 0.5f);
}
@Override
@@ -127,7 +122,7 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
this.getRidingEntity().attackEntityFrom(DamageSource.MAGIC, 1);
((EntityLivingBase)this.getRidingEntity())
.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 20, 2));
this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 1.0f, 1.0f);
this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_ATTACK, 1.0f, 1.0f);
this.squishAmount = 0.5F;
}
}else{
@@ -174,7 +169,10 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature {
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
}
@@ -1,9 +1,7 @@
package electroblob.wizardry.entity.living;
import java.util.Collections;
import java.util.List;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
@@ -13,7 +11,6 @@ import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.init.SoundEvents;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.SoundEvent;
@@ -22,6 +19,9 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.Collections;
import java.util.List;
public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaster {
private double AISpeed = 0.5;
@@ -97,17 +97,17 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
@Override
protected SoundEvent getAmbientSound(){
return SoundEvents.ENTITY_BLAZE_AMBIENT;
return WizardrySounds.ENTITY_PHOENIX_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource source){
return SoundEvents.ENTITY_BLAZE_HURT;
return WizardrySounds.ENTITY_PHOENIX_HURT;
}
@Override
protected SoundEvent getDeathSound(){
return SoundEvents.ENTITY_BLAZE_DEATH;
return WizardrySounds.ENTITY_PHOENIX_DEATH;
}
@Override
@@ -144,9 +144,9 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
public void onLivingUpdate(){
// Makes the phoenix hover.
int floorLevel = WizardryUtilities.getNearestFloorLevel(world, new BlockPos(this), 4);
Integer floorLevel = WizardryUtilities.getNearestFloor(world, new BlockPos(this), 4);
if(this.posY - floorLevel > 3){
if(floorLevel == null || this.posY - floorLevel > 3){
this.motionY = -0.1;
}else if(this.posY - floorLevel < 2){
this.motionY = 0.1;
@@ -156,13 +156,13 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
// Living sound
if(this.rand.nextInt(24) == 0){
this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 1.0F + this.rand.nextFloat(),
this.playSound(WizardrySounds.ENTITY_PHOENIX_BURN, 1.0F + this.rand.nextFloat(),
this.rand.nextFloat() * 0.7F + 0.3F);
}
// Flapping sound effect
if(this.ticksExisted % 22 == 0){
this.playSound(SoundEvents.ENTITY_ENDERDRAGON_FLAP, 1.0F, 1.0f);
this.playSound(WizardrySounds.ENTITY_PHOENIX_FLAP, 1.0F, 1.0f);
}
for(int i = 0; i < 2; i++){
@@ -1,22 +1,15 @@
package electroblob.wizardry.entity.living;
import java.util.Collections;
import java.util.List;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.*;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumParticleTypes;
@@ -25,6 +18,9 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.Collections;
import java.util.List;
public class EntityShadowWraith extends EntitySummonedCreature implements ISpellCaster {
// TODO: This currently doesn't fly like it used to. Should it, or does it not matter?
@@ -96,17 +92,17 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell
@Override
protected SoundEvent getAmbientSound(){
return SoundEvents.ENTITY_BLAZE_AMBIENT;
return WizardrySounds.ENTITY_SHADOW_WRAITH_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource source){
return SoundEvents.ENTITY_BLAZE_HURT;
return WizardrySounds.ENTITY_SHADOW_WRAITH_HURT;
}
@Override
protected SoundEvent getDeathSound(){
return SoundEvents.ENTITY_BLAZE_DEATH;
return WizardrySounds.ENTITY_SHADOW_WRAITH_DEATH;
}
@Override
@@ -135,7 +131,7 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell
public void onLivingUpdate(){
if(this.rand.nextInt(24) == 0){
this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.0F + this.rand.nextFloat(),
this.playSound(WizardrySounds.ENTITY_SHADOW_WRAITH_NOISE, 1.0F + this.rand.nextFloat(),
this.rand.nextFloat() * 0.7F + 0.3F);
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.UUID;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
@@ -20,22 +17,26 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import java.util.UUID;
public class EntitySilverfishMinion extends EntitySilverfish implements ISummonedCreature {
public static final int MAX_GENERATIONS = 5;
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
private int generation = 1;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
@Override public UUID getCasterUUID(){ return casterUUID; }
@Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
/** Creates a new silverfish minion in the given world. */
public EntitySilverfishMinion(World world){
@@ -50,7 +51,7 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
this.tasks.addTask(1, new EntityAISwimming(this));
this.tasks.addTask(4, new EntityAIAttackMelee(this, 1.0D, false));
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
0, false, true, this.getTargetSelector()));
}
@@ -99,7 +100,7 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
public void onKillEntity(EntityLivingBase victim){
// If the silverfish has a summoner, this is actually called from Wizardry's event handler rather than by
// Minecraft itself, because the damagesource being changed causes it not to get called.
if(!this.world.isRemote){
if(!this.world.isRemote && generation < MAX_GENERATIONS){
// Summons 1-4 more silverfish
int alliesToSummon = rand.nextInt(4) + 1;
@@ -108,6 +109,7 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
silverfish.setPosition(victim.posX, victim.posY, victim.posZ);
silverfish.setCaster(this.getCaster());
silverfish.setLifetime(this.getLifetime());
silverfish.generation = this.generation + 1;
this.world.spawnEntity(silverfish);
}
}
@@ -129,12 +131,14 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
super.writeEntityToNBT(nbttagcompound);
this.writeNBTDelegate(nbttagcompound);
nbttagcompound.setInteger("generation", this.generation);
}
@Override
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
super.readEntityFromNBT(nbttagcompound);
this.readNBTDelegate(nbttagcompound);
this.generation = nbttagcompound.getInteger("generation");
}
// Recommended overrides
@@ -144,8 +148,16 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -166,6 +178,6 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,12 +1,7 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.Calendar;
import java.util.UUID;
import javax.annotation.Nullable;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.WizardryUtilities.Operations;
import net.minecraft.entity.EntityFlying;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityLivingData;
@@ -14,37 +9,39 @@ import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.monster.EntitySkeleton;
import net.minecraft.entity.monster.AbstractSkeleton;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.*;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCreature {
import javax.annotation.Nullable;
import java.util.Calendar;
import java.util.UUID;
// Extends AbstractSkeleton because EntitySkeleton drops skulls, which we don't want
public class EntitySkeletonMinion extends AbstractSkeleton implements ISummonedCreature {
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
@Override public UUID getCasterUUID(){ return casterUUID; }
@Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
/** Creates a new skeleton minion in the given world. */
public EntitySkeletonMinion(World world){
@@ -55,13 +52,13 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
// EntitySkeleton overrides
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
// targeting system with one that targets hostile mobs and takes the ADS into account.
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account.
@Override
protected void initEntityAI(){
super.initEntityAI();
this.targetTasks.taskEntries.clear();
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
0, false, true, this.getTargetSelector()));
}
@@ -76,7 +73,7 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){
// Can't call super, so the code from the next level up (EntityLiving) had to be copied as well.
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE)
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1));
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT));
if(this.rand.nextFloat() < 0.05F){
this.setLeftHanded(true);
@@ -98,6 +95,12 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
return livingdata;
}
// Since we're extending AbstractSkeleton these aren't set by the superclass like normal
@Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_SKELETON_AMBIENT; }
@Override protected SoundEvent getHurtSound(DamageSource source){ return SoundEvents.ENTITY_SKELETON_HURT; }
@Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_SKELETON_DEATH; }
@Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_SKELETON_STEP; }
// Implementations
@Override
@@ -161,8 +164,16 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -183,6 +194,6 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,11 +1,9 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.UUID;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities.Operations;
import net.minecraft.entity.EntityFlying;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityLivingData;
@@ -27,20 +25,19 @@ import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import java.util.UUID;
public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCreature {
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
@Override public UUID getCasterUUID(){ return casterUUID; }
@Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
/** Creates a new spider minion in the given world. */
public EntitySpiderMinion(World world){
@@ -51,7 +48,7 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
// EntitySpider overrides
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
// targeting system with one that targets hostile mobs and takes the ADS into account.
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account.
@Override
protected void initEntityAI(){
super.initEntityAI();
@@ -70,7 +67,7 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
// Can't call super, so the code from the next level up (EntityLiving) had to be copied as well.
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE)
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1));
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT));
if(this.rand.nextFloat() < 0.05F){
this.setLeftHanded(true);
@@ -163,8 +160,16 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -185,6 +190,6 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,8 +1,7 @@
package electroblob.wizardry.entity.living;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
@@ -16,7 +15,6 @@ import net.minecraft.entity.passive.EntityHorse;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
@@ -34,6 +32,10 @@ public class EntitySpiritHorse extends EntityHorse {
private int idleTimer = 0;
private int dispelTimer = 0;
private static final int DISPEL_TIME = 10;
public EntitySpiritHorse(World par1World){
super(par1World);
}
@@ -93,19 +95,13 @@ public class EntitySpiritHorse extends EntityHorse {
// Allows the owner (but not other players) to dispel the spirit horse using a wand (shift-clicking, because
// clicking mounts the horse in this case).
if(itemstack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
if(itemstack.getItem() instanceof ISpellCastingItem && this.getOwner() == player && player.isSneaking()){
// Prevents accidental double clicking.
if(this.ticksExisted > 20){
this.dispelTimer++;
this.spawnAppearParticles();
this.setDead();
if(WizardData.get(player) != null){
WizardData.get(player).hasSpiritHorse = false;
}
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
this.playSound(WizardrySounds.ENTITY_SPIRIT_HORSE_VANISH, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
// This is necessary to prevent the wand's spell being cast when performing this action.
return true;
}
@@ -124,17 +120,6 @@ public class EntitySpiritHorse extends EntityHorse {
}
}
@Override
public void onDeath(DamageSource par1DamageSource){
super.onDeath(par1DamageSource);
// Allows player to summon another spirit horse once this one has died.
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false;
}
}
// I wrote this one!
private EntityLivingBase getOwner(){
@@ -148,11 +133,21 @@ public class EntitySpiritHorse extends EntityHorse {
}
}
public float getOpacity(){
return 1 - (float)dispelTimer/DISPEL_TIME;
}
@Override
public void onUpdate(){
super.onUpdate();
if(dispelTimer > 0){
if(dispelTimer++ > DISPEL_TIME){
this.setDead();
}
}
// Adds a dust particle effect
if(this.world.isRemote){
double x = this.posX - this.width / 2 + this.rand.nextFloat() * width;
@@ -170,17 +165,9 @@ public class EntitySpiritHorse extends EntityHorse {
if(this.idleTimer > 200){
if(this.world.isRemote){
this.spawnAppearParticles();
}
this.playSound(WizardrySounds.ENTITY_SPIRIT_HORSE_VANISH, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
// Allows player to summon another spirit horse once this one has disappeared.
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false;
}
this.setDead();
this.dispelTimer++;
}
}
@@ -213,7 +200,7 @@ public class EntitySpiritHorse extends EntityHorse {
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getOwner() != null;
return Wizardry.settings.summonedCreatureNames && getOwner() != null;
}
}
@@ -1,29 +1,17 @@
package electroblob.wizardry.entity.living;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.EntityAgeable;
import net.minecraft.entity.IEntityLivingData;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.EntityAIFollowOwner;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILeapAtTarget;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAIOwnerHurtByTarget;
import net.minecraft.entity.ai.EntityAIOwnerHurtTarget;
import net.minecraft.entity.ai.EntityAISit;
import net.minecraft.entity.ai.EntityAISwimming;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.ai.*;
import net.minecraft.entity.passive.EntityWolf;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
@@ -37,9 +25,12 @@ import net.minecraft.world.World;
*/
public class EntitySpiritWolf extends EntityWolf {
public EntitySpiritWolf(World par1World){
private int dispelTimer = 0;
super(par1World);
private static final int DISPEL_TIME = 10;
public EntitySpiritWolf(World world){
super(world);
this.experienceValue = 0;
}
@@ -60,18 +51,6 @@ public class EntitySpiritWolf extends EntityWolf {
this.targetTasks.addTask(3, new EntityAIHurtByTarget(this, true, new Class[0]));
}
@Override
public void onDeath(DamageSource source){
// Allows player to summon another spirit wolf once this one has died.
// NOTE: This has been known to work incorrectly.
if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){
WizardData.get((EntityPlayer)this.getOwner()).hasSpiritWolf = false;
}
super.onDeath(source);
}
@Override
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata){
@@ -92,12 +71,22 @@ public class EntitySpiritWolf extends EntityWolf {
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).clr(0.8f, 0.8f, 1.0f).spawn(world);
}
}
public float getOpacity(){
return 1 - (float)dispelTimer/DISPEL_TIME;
}
@Override
public void onUpdate(){
super.onUpdate();
if(dispelTimer > 0){
if(dispelTimer++ > DISPEL_TIME){
this.setDead();
}
}
// Adds a dust particle effect
if(this.world.isRemote){
double x = this.posX - this.width / 2 + this.rand.nextFloat() * width;
@@ -116,21 +105,13 @@ public class EntitySpiritWolf extends EntityWolf {
// Allows the owner (but not other players) to dispel the spirit wolf using a
// wand.
if(stack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){
if(stack.getItem() instanceof ISpellCastingItem && this.getOwner() == player && player.isSneaking()){
// Prevents accidental double clicking.
if(this.ticksExisted > 20){
if(this.world.isRemote){
this.spawnAppearParticles();
}
this.dispelTimer++;
this.setDead();
if(WizardData.get(player) != null){
WizardData.get(player).hasSpiritWolf = false;
}
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
this.playSound(WizardrySounds.ENTITY_SPIRIT_WOLF_VANISH, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
// This is necessary to prevent the wand's spell being cast when performing this
// action.
return true;
@@ -181,7 +162,7 @@ public class EntitySpiritWolf extends EntityWolf {
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking
// directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getOwner() != null;
return Wizardry.settings.summonedCreatureNames && getOwner() != null;
}
}
@@ -1,23 +1,15 @@
package electroblob.wizardry.entity.living;
import java.util.Collections;
import java.util.List;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.*;
import net.minecraft.entity.effect.EntityLightningBolt;
import net.minecraft.init.SoundEvents;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.SoundEvent;
@@ -25,6 +17,9 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.Collections;
import java.util.List;
public class EntityStormElemental extends EntitySummonedCreature implements ISpellCaster {
private double AISpeed = 1.0;
@@ -90,17 +85,17 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe
@Override
protected SoundEvent getAmbientSound(){
return SoundEvents.ENTITY_BLAZE_AMBIENT;
return WizardrySounds.ENTITY_STORM_ELEMENTAL_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource source){
return SoundEvents.ENTITY_BLAZE_HURT;
return WizardrySounds.ENTITY_STORM_ELEMENTAL_HURT;
}
@Override
protected SoundEvent getDeathSound(){
return SoundEvents.ENTITY_BLAZE_DEATH;
return WizardrySounds.ENTITY_STORM_ELEMENTAL_DEATH;
}
@Override
@@ -118,11 +113,11 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe
public void onLivingUpdate(){
if(this.ticksExisted % 120 == 1){
this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f);
this.playSound(WizardrySounds.ENTITY_STORM_ELEMENTAL_WIND, 1.0f, 1.0f);
}
if(this.rand.nextInt(24) == 0){
this.playSound(SoundEvents.ENTITY_BLAZE_BURN, 1.0F + this.rand.nextFloat(),
this.playSound(WizardrySounds.ENTITY_STORM_ELEMENTAL_BURN, 1.0F + this.rand.nextFloat(),
this.rand.nextFloat() * 0.7F + 0.3F);
}
@@ -0,0 +1,35 @@
package electroblob.wizardry.entity.living;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.entity.projectile.EntityTippedArrow;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.World;
public class EntityStrayMinion extends EntitySkeletonMinion {
/** Creates a new stray minion in the given world. */
public EntityStrayMinion(World world){
super(world);
}
@Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_STRAY_AMBIENT; }
@Override protected SoundEvent getHurtSound(DamageSource source){ return SoundEvents.ENTITY_STRAY_HURT; }
@Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_STRAY_DEATH; }
@Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_STRAY_STEP; }
@Override
protected EntityArrow getArrow(float distanceFactor){
EntityArrow entityarrow = super.getArrow(distanceFactor);
if(entityarrow instanceof EntityTippedArrow){
((EntityTippedArrow)entityarrow).addEffect(new PotionEffect(MobEffects.SLOWNESS, 600));
}
return entityarrow;
}
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.UUID;
import electroblob.wizardry.Wizardry;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.EntityFlying;
@@ -14,8 +11,11 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import java.util.UUID;
/**
* Abstract base implementation of {@link ISummonedCreature} which is the superclass to all custom summoned entities
* (i.e. entities that don't extend vanilla/mod creatures). Also serves as an example of how to correctly implement the
@@ -29,8 +29,7 @@ import net.minecraft.world.World;
public abstract class EntitySummonedCreature extends EntityCreature implements ISummonedCreature {
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@@ -45,22 +44,12 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I
}
@Override
public WeakReference<EntityLivingBase> getCasterReference(){
return casterReference;
}
@Override
public void setCasterReference(WeakReference<EntityLivingBase> reference){
casterReference = reference;
}
@Override
public UUID getCasterUUID(){
public UUID getOwnerId(){
return casterUUID;
}
@Override
public void setCasterUUID(UUID uuid){
public void setOwnerId(UUID uuid){
this.casterUUID = uuid;
}
@@ -122,8 +111,16 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -144,7 +141,7 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
// Specific to EntitySummonedCreature, remove if copying
@@ -0,0 +1,151 @@
package electroblob.wizardry.entity.living;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.monster.EntityVex;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.UUID;
public class EntityVexMinion extends EntityVex implements ISummonedCreature {
// Field implementations
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
/** Creates a new vex minion in the given world. */
public EntityVexMinion(World world){
super(world);
this.experienceValue = 0;
}
// ISummonedCreature overrides
@Override
public void setCaster(@Nullable EntityLivingBase caster){
// Integrates the summoned creature caster system with the (subtly different) vex owner system for NPC casters
ISummonedCreature.super.setCaster(caster);
if(caster instanceof EntityLiving) this.setOwner((EntityLiving)caster);
}
// EntityVex overrides
@Override
protected void initEntityAI(){
super.initEntityAI();
this.targetTasks.taskEntries.clear();
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
0, false, false, this.getTargetSelector()));
}
// Implementations
@Override
public void setRevengeTarget(EntityLivingBase entity){
if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity);
}
@Override
public void onUpdate(){
super.onUpdate();
this.updateDelegate();
}
@Override
public void onSpawn(){
this.spawnParticleEffect();
}
@Override
public void onDespawn(){
this.spawnParticleEffect();
}
private void spawnParticleEffect(){
if(this.world.isRemote){
for(int i = 0; i < 15; i++){
ParticleBuilder.create(Type.DARK_MAGIC)
.pos(this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat())
.clr(0.3f, 0.3f, 0.3f)
.spawn(world);
}
}
}
@Override
public boolean hasParticleEffect(){
return true;
}
@Override
protected boolean processInteract(EntityPlayer player, EnumHand hand){
// In this case, the delegate method determines whether super is called.
// Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements.
return this.interactDelegate(player, hand) || super.processInteract(player, hand);
}
@Override
public void writeEntityToNBT(NBTTagCompound nbttagcompound){
super.writeEntityToNBT(nbttagcompound);
this.writeNBTDelegate(nbttagcompound);
}
@Override
public void readEntityFromNBT(NBTTagCompound nbttagcompound){
super.readEntityFromNBT(nbttagcompound);
this.readNBTDelegate(nbttagcompound);
}
// Recommended overrides
@Override protected int getExperiencePoints(EntityPlayer player){ return 0; }
@Override protected boolean canDropLoot(){ return false; }
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public ITextComponent getDisplayName(){
if(getCaster() != null){
return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(),
new TextComponentTranslation("entity." + this.getEntityString() + ".name"));
}else{
return super.getDisplayName();
}
}
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,12 +1,7 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.Calendar;
import java.util.UUID;
import javax.annotation.Nullable;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.WizardryUtilities.Operations;
import net.minecraft.entity.EntityFlying;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityLivingData;
@@ -31,22 +26,24 @@ import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Calendar;
import java.util.UUID;
public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements ISummonedCreature {
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
@Override public UUID getCasterUUID(){ return casterUUID; }
@Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
/** Creates a new wither skeleton minion in the given world. */
public EntityWitherSkeletonMinion(World world){
@@ -57,7 +54,7 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
// EntitySkeleton overrides
// This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its
// targeting system with one that targets hostile mobs and takes the ADS into account.
// targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account.
@Override
protected void initEntityAI(){
super.initEntityAI();
@@ -78,7 +75,7 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){
// Can't call super, so the code from the next level up (EntityLiving) had to be copied as well.
this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE)
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1));
.applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT));
if(this.rand.nextFloat() < 0.05F){
this.setLeftHanded(true);
@@ -171,8 +168,16 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -193,6 +198,6 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,70 +1,37 @@
package electroblob.wizardry.entity.living;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Predicate;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.misc.WildcardTradeList;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WildcardTradeList;
import electroblob.wizardry.util.WizardryUtilities;
import electroblob.wizardry.util.*;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.IEntityLivingData;
import net.minecraft.entity.IMerchant;
import net.minecraft.entity.INpc;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAIOpenDoor;
import net.minecraft.entity.ai.EntityAIRestrictOpenDoor;
import net.minecraft.entity.ai.EntityAISwimming;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.ai.EntityAIWatchClosest2;
import net.minecraft.entity.*;
import net.minecraft.entity.ai.*;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagInt;
import net.minecraft.nbt.NBTTagLong;
import net.minecraft.nbt.*;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
@@ -73,6 +40,7 @@ import net.minecraft.village.MerchantRecipeList;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.world.BlockEvent;
@@ -83,6 +51,9 @@ import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
import javax.annotation.Nullable;
import java.util.*;
@Mod.EventBusSubscriber
public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISpellCaster, IEntityAdditionalSpawnData {
@@ -150,7 +121,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// If the target is valid and not invisible...
if(entity != null && !entity.isInvisible()
&& WizardryUtilities.isValidTarget(EntityWizard.this, entity)){
&& AllyDesignationSystem.isValidTarget(EntityWizard.this, entity)){
// ... and is a mob, a summoned creature ...
if((entity instanceof IMob || entity instanceof ISummonedCreature
@@ -247,7 +218,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Copied from EntityVillager
if(!this.world.isRemote && this.livingSoundTime > -this.getTalkInterval() + 20){
this.livingSoundTime = -this.getTalkInterval();
this.playSound(stack.isEmpty() ? SoundEvents.ENTITY_VILLAGER_NO : SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch());
this.playSound(stack.isEmpty() ? WizardrySounds.ENTITY_WIZARD_NO : WizardrySounds.ENTITY_WIZARD_YES, this.getSoundVolume(), this.getSoundPitch());
}
}
@@ -267,8 +238,14 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Apparently nothing goes here, and nothing's here in EntityVillager either...
}
// TESTME: Should this be getName instead?
@Override
public ITextComponent getDisplayName(){
if(this.hasCustomName()){
return super.getDisplayName();
}
return this.getElement().getWizardName();
}
@@ -276,6 +253,21 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
protected boolean canDespawn(){
return false;
}
@Override
protected SoundEvent getAmbientSound(){
return this.isTrading() ? WizardrySounds.ENTITY_WIZARD_TRADING : WizardrySounds.ENTITY_WIZARD_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource source){
return WizardrySounds.ENTITY_WIZARD_HURT;
}
@Override
protected SoundEvent getDeathSound(){
return WizardrySounds.ENTITY_WIZARD_DEATH;
}
@Override
public void onLivingUpdate(){
@@ -301,14 +293,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Heal particles TODO: Change this so it uses the heal spell directly
if(world.isRemote){
for(int i=0; i<10; i++){
double x = (double)((float)this.posX + rand.nextFloat() * 2 - 1.0F);
// Apparently the client side spawns the particles 1 block higher than it should... hence the -
// 0.5F.
double y = (double)((float)this.posY - 0.5F + rand.nextFloat());
double z = (double)((float)this.posZ + rand.nextFloat() * 2 - 1.0F);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1F, 0).clr(1, 1, 0.3f).spawn(world);
}
ParticleBuilder.spawnHealParticles(world, this);
}else{
if(this.getHealth() < 10){
// Wizards heal themseselves more often if they have low health
@@ -317,7 +302,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
this.setHealCooldown(400);
}
this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F);
this.playSound(Spells.heal.getSounds()[0], 0.7F, rand.nextFloat() * 0.4F + 1.0F);
}
}
@@ -370,8 +355,8 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Spell.get(spells[3]).getDisplayName()));
// When right-clicked with a spell book in creative, sets one of the spells to that spell
if(player.capabilities.isCreativeMode && stack.getItem() instanceof ItemSpellBook){
Spell spell = Spell.get(stack.getItemDamage());
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
Spell spell = Spell.byMetadata(stack.getItemDamage());
if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){
// The set(...) method returns the element that was replaced - neat!
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
@@ -407,11 +392,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
nbt.setInteger("element", this.getElement().ordinal());
nbt.setInteger("skin", this.textureIndex);
nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id())));
nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata())));
if(this.towerBlocks != null && this.towerBlocks.size() > 0){
nbt.setTag("towerBlocks",
WizardryUtilities.listToNBT(this.towerBlocks, pos -> new NBTTagLong(pos.toLong())));
nbt.setTag("towerBlocks", NBTExtras.listToNBT(this.towerBlocks, NBTUtil::createPosTag));
}
}
@@ -427,11 +411,17 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
this.setElement(Element.values()[nbt.getInteger("element")]);
this.textureIndex = nbt.getInteger("skin");
this.spells = (List<Spell>)WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
(NBTTagInt tag) -> Spell.get(tag.getInt()));
this.spells = (List<Spell>)NBTExtras.NBTToList(nbt.getTagList("spells", NBT.TAG_INT),
(NBTTagInt tag) -> Spell.byMetadata(tag.getInt()));
this.towerBlocks = new HashSet<BlockPos>(WizardryUtilities.NBTToList(
nbt.getTagList("towerBlocks", NBT.TAG_LONG), (NBTTagLong tag) -> BlockPos.fromLong(tag.getLong())));
NBTTagList tagList = nbt.getTagList("towerBlocks", NBT.TAG_LONG);
if(!tagList.isEmpty()){
this.towerBlocks = new HashSet<>(NBTExtras.NBTToList(tagList, NBTUtil::getPosFromTag));
}else{
// Fallback to old packed long format
this.towerBlocks = new HashSet<>(NBTExtras.NBTToList(nbt.getTagList("towerBlocks", NBT.TAG_LONG),
(NBTTagLong tag) -> BlockPos.fromLong(tag.getLong())));
}
}
@Override
@@ -439,20 +429,42 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
merchantrecipe.incrementToolUses();
this.livingSoundTime = -this.getTalkInterval();
this.playSound(SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch());
this.playSound(WizardrySounds.ENTITY_WIZARD_YES, this.getSoundVolume(), this.getSoundPitch());
// Achievements
if(this.getCustomer() != null){
// Achievements
WizardryAdvancementTriggers.wizard_trade.triggerFor(this.getCustomer());
if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook
&& Spell.get(merchantrecipe.getItemToSell().getItemDamage()).tier == Tier.MASTER){
WizardryAdvancementTriggers.buy_master_spell.triggerFor(this.getCustomer());
if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook){
Spell spell = Spell.byMetadata(merchantrecipe.getItemToSell().getItemDamage());
if(spell.getTier() == Tier.MASTER) WizardryAdvancementTriggers.buy_master_spell.triggerFor(this.getCustomer());
// Spell discovery (a lot of this is the same as in the event handler)
WizardData data = WizardData.get(this.getCustomer());
if(data != null){
if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(this.getCustomer(), spell,
DiscoverSpellEvent.Source.PURCHASE)) && data.discoverSpell(spell)){
data.sync();
if(!world.isRemote && !this.getCustomer().isCreative() && Wizardry.settings.discoveryMode){
// Sound and text only happen server-side, in survival, with discovery mode on
WizardryUtilities.playSoundAtPlayer(this.getCustomer(), WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
this.getCustomer().sendMessage(new TextComponentTranslation("spell.discover",
spell.getNameForTranslationFormatted()));
}
}
}
}
}
// Changed to a 4 in 5 chance of unlocking a new recipe.
if(this.rand.nextInt(5) > 0){
if(this.rand.nextInt(5) > 0 || ItemArtefact.isArtefactActive(customer, WizardryItems.charm_haggler)){
this.timeUntilReset = 40;
this.updateRecipes = true;
@@ -500,7 +512,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
boolean itemAlreadySold = true;
Tier tier = Tier.BASIC;
Tier tier = Tier.NOVICE;
while(itemAlreadySold){
@@ -515,7 +527,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
double tierIncreaseChance = 0.5 + 0.04 * (Math.max(this.trades.size() - 4, 0));
tier = Tier.BASIC;
tier = Tier.NOVICE;
if(rand.nextDouble() < tierIncreaseChance){
tier = Tier.APPRENTICE;
@@ -545,8 +557,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Don't know how it can ever be empty here, but it's a failsafe.
if(itemToSell.isEmpty()) return;
merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier),
new ItemStack(WizardryItems.magic_crystal, tier.ordinal() * 3 + 1 + rand.nextInt(4)), itemToSell));
ItemStack secondItemToBuy = tier == Tier.MASTER ? new ItemStack(WizardryItems.astral_diamond)
: new ItemStack(WizardryItems.magic_crystal, tier.ordinal() * 3 + 1 + rand.nextInt(4));
merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier), secondItemToBuy, itemToSell));
}
Collections.shuffle(merchantrecipelist);
@@ -555,27 +569,31 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
this.trades = new WildcardTradeList();
}
for(int j1 = 0; j1 < merchantrecipelist.size(); ++j1){
this.trades.add(merchantrecipelist.get(j1));
}
this.trades.addAll(merchantrecipelist);
}
// TODO: Switch all of this over to some kind of loot pool system?
private ItemStack getRandomPrice(Tier tier){
ItemStack itemstack = ItemStack.EMPTY;
switch(this.rand.nextInt(3)){
case 0:
itemstack = new ItemStack(Items.GOLD_INGOT, (tier.ordinal() + 1) * 8 - 1 + rand.nextInt(6));
break;
case 1:
itemstack = new ItemStack(Items.DIAMOND, (tier.ordinal() + 1) * 4 - 2 + rand.nextInt(3));
break;
case 2:
itemstack = new ItemStack(Items.EMERALD, (tier.ordinal() + 1) * 6 - 1 + rand.nextInt(3));
break;
Map<ResourceLocation, Integer> map = Wizardry.settings.currencyItems;
// This isn't that efficient but it's not called very often really so it doesn't matter
ResourceLocation itemName = map.keySet().toArray(new ResourceLocation[0])[rand.nextInt(map.size())];
Item item = Item.REGISTRY.getObject(itemName);
int value;
if(item == null){
Wizardry.logger.warn("Invalid item in currency items: {}", itemName);
item = Items.EMERALD; // Fallback item
value = 6;
}else{
value = map.get(itemName);
}
return itemstack;
// ((tier.ordinal() + 1) * 16 + rand.nextInt(6)) gives a 'value' for the item being bought
// This is then divided by the value of the currency item to give a price
// The absolute maximum stack size that can result from this calculation (with value = 1) is 64.
return new ItemStack(item, (8 + tier.ordinal() * 16 + rand.nextInt(9)) / value);
}
private ItemStack getRandomItemOfTier(Tier tier){
@@ -583,32 +601,36 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
int randomiser;
// All enabled spells of the given tier
List<Spell> spells = Spell.getSpells(new Spell.TierElementFilter(tier, null));
List<Spell> spells = Spell.getSpells(new Spell.TierElementFilter(tier, null, SpellProperties.Context.TRADES));
// All enabled spells of the given tier that match this wizard's element
List<Spell> specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement()));
List<Spell> specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement(), SpellProperties.Context.TRADES));
// Wizards don't sell scrolls
spells.removeIf(s -> !s.isEnabled(SpellProperties.Context.BOOK));
specialismSpells.removeIf(s -> !s.isEnabled(SpellProperties.Context.BOOK));
// This code is sooooooo much neater with the new filter system!
switch(tier){
case BASIC:
case NOVICE:
randomiser = rand.nextInt(5);
if(randomiser < 4 && !spells.isEmpty()){
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){
// This means it is more likely for spell books sold to be of the same element as the wizard if the
// wizard has an element.
return new ItemStack(WizardryItems.spell_book, 1,
specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
}else{
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata());
}
}else{
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard
// has an element.
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
}else{
return new ItemStack(
WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
}
}
@@ -619,18 +641,18 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// This means it is more likely for spell books sold to be of the same element as the wizard if the
// wizard has an element.
return new ItemStack(WizardryItems.spell_book, 1,
specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
}else{
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata());
}
}else if(randomiser < 6){
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard
// has an element.
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
}else{
return new ItemStack(
WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
}
}else if(randomiser < 8){
return new ItemStack(WizardryItems.arcane_tome, 1, 1);
@@ -639,10 +661,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
// This means it is more likely for armour sold to be of the same element as the wizard if the
// wizard has an element.
return new ItemStack(WizardryUtilities.getArmour(this.getElement(), slot));
return new ItemStack(WizardryItems.getArmour(this.getElement(), slot));
}else{
return new ItemStack(
WizardryUtilities.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot));
WizardryItems.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot));
}
}else{
// Don't need to check for discovery mode here since it is done above
@@ -656,18 +678,18 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// This means it is more likely for spell books sold to be of the same element as the wizard if the
// wizard has an element.
return new ItemStack(WizardryItems.spell_book, 1,
specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
}else{
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id());
return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata());
}
}else if(randomiser < 6){
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
// This means it is more likely for wands sold to be of the same element as the wizard if the wizard
// has an element.
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
}else{
return new ItemStack(
WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)]));
}
}else if(randomiser < 8){
return new ItemStack(WizardryItems.arcane_tome, 1, 2);
@@ -684,12 +706,12 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
if(randomiser < 5 && this.getElement() != Element.MAGIC && !specialismSpells.isEmpty()){
// Master spells can only be sold by a specialist in that element.
return new ItemStack(WizardryItems.spell_book, 1,
specialismSpells.get(rand.nextInt(specialismSpells.size())).id());
specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata());
}else if(randomiser < 6){
if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){
// Master elemental wands can only be sold by a specialist in that element.
return new ItemStack(WizardryUtilities.getWand(tier, this.getElement()));
return new ItemStack(WizardryItems.getWand(tier, this.getElement()));
}else{
return new ItemStack(WizardryItems.master_wand);
}
@@ -718,7 +740,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Adds armour.
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot)));
this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot)));
}
// Default chance is 0.085f, for reference.
@@ -728,11 +750,16 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// All wizards know magic missile, even if it is disabled.
spells.add(Spells.magic_missile);
Tier maxTier = populateSpells(spells, element, 3, rand);
Tier maxTier = populateSpells(spells, element, false, 3, rand);
// Now done after the spells so it can take the tier into account.
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND,
new ItemStack(WizardryUtilities.getWand(maxTier, element)));
ItemStack wand = new ItemStack(WizardryItems.getWand(maxTier, element));
ArrayList<Spell> list = new ArrayList<>(spells);
list.add(Spells.heal);
WandHelper.setSpells(wand, list.toArray(new Spell[5]));
this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, wand);
this.setHealCooldown(50);
return livingdata;
}
@@ -747,14 +774,14 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
* @param random A random number generator to use.
* @return The tier of the highest-tier spell that was added to the list.
*/
static Tier populateSpells(List<Spell> spells, Element e, int n, Random random){
static Tier populateSpells(List<Spell> spells, Element e, boolean master, int n, Random random){
// This is the tier of the highest tier spell added.
Tier maxTier = Tier.BASIC;
Tier maxTier = Tier.NOVICE;
List<Spell> npcSpells = Spell.getSpells(Spell.npcSpells);
for(int i = 0; i < 3; i++){
for(int i = 0; i < n; i++){
Tier tier;
// If the wizard has no element, it picks a random one each time.
@@ -764,17 +791,19 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Uses its own special weighting
if(randomiser < 10){
tier = Tier.BASIC;
tier = Tier.NOVICE;
}else if(randomiser < 16){
tier = Tier.APPRENTICE;
}else{
}else if(randomiser < 19 || !master){
tier = Tier.ADVANCED;
}else{
tier = Tier.MASTER;
}
if(tier.ordinal() > maxTier.ordinal()) maxTier = tier;
// Finds all the spells of the chosen tier and element
List<Spell> list = Spell.getSpells(new Spell.TierElementFilter(tier, element));
List<Spell> list = Spell.getSpells(new Spell.TierElementFilter(tier, element, SpellProperties.Context.NPCS));
// Keeps only spells which can be cast by NPCs
list.retainAll(npcSpells);
// Removes spells that the wizard already has
@@ -828,12 +857,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
this.towerBlocks = blocks;
}
/**
* Tests whether the block at the given coordinates is part of this wizard's tower.
*
* @param pos
* @return
*/
/** Tests whether the block at the given coordinates is part of this wizard's tower. */
public boolean isBlockPartOfTower(BlockPos pos){
if(this.towerBlocks == null) return false;
// Uses .equals() rather than == so this will work fine.
@@ -1,8 +1,5 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.UUID;
import electroblob.wizardry.Wizardry;
import net.minecraft.entity.EntityFlying;
import net.minecraft.entity.EntityLivingBase;
@@ -12,6 +9,7 @@ import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
@@ -19,22 +17,22 @@ import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import java.util.UUID;
public class EntityZombieMinion extends EntityZombie implements ISummonedCreature {
// Field implementations
private int lifetime = 600;
private WeakReference<EntityLivingBase> casterReference;
private int lifetime = -1;
private UUID casterUUID;
// Setter + getter implementations
@Override public int getLifetime(){ return lifetime; }
@Override public void setLifetime(int lifetime){ this.lifetime = lifetime; }
@Override public WeakReference<EntityLivingBase> getCasterReference(){ return casterReference; }
@Override public void setCasterReference(WeakReference<EntityLivingBase> reference){ casterReference = reference; }
@Override public UUID getCasterUUID(){ return casterUUID; }
@Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; }
@Override public UUID getOwnerId(){ return casterUUID; }
@Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; }
/** Creates a new zombie minion in the given world. */
public EntityZombieMinion(World world){
@@ -42,13 +40,13 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
this.experienceValue = 0;
}
// EntityZombie overrides (EntityZombie is a long class so there are lots of these)
// EntityZombie overrides (EntityZombie is a complex class so there are lots of these)
@Override
protected void applyEntityAI(){
this.tasks.addTask(6, new EntityAIMoveThroughVillage(this, 1.0D, false));
this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityLivingBase>(this, EntityLivingBase.class,
this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class,
0, false, true, this.getTargetSelector()));
}
@@ -57,6 +55,7 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
@Override protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty){} // They don't have equipment!
@Override public void onKillEntity(EntityLivingBase entityLivingIn){} // Turns villagers to zombies in EntityZombie
@Override public void setChildSize(boolean isChild){}
@Override protected ItemStack getSkullDrop(){ return ItemStack.EMPTY; }
// Implementations
@@ -121,8 +120,16 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
@Override protected Item getDropItem(){ return null; }
@Override protected ResourceLocation getLootTable(){ return null; }
@Override public boolean canPickUpLoot(){ return false; }
// This vanilla method has nothing to do with the custom despawn() method.
@Override protected boolean canDespawn(){ return false; }
@Override protected boolean canDespawn(){
return getCaster() == null && getOwnerId() == null;
}
@Override
public boolean getCanSpawnHere(){
return this.world.getDifficulty() != EnumDifficulty.PEACEFUL;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> entityType){
@@ -143,7 +150,7 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur
@Override
public boolean hasCustomName(){
// If this returns true, the renderer will show the nameplate when looking directly at the entity
return Wizardry.settings.showSummonedCreatureNames && getCaster() != null;
return Wizardry.settings.summonedCreatureNames && getCaster() != null;
}
}
@@ -1,9 +1,9 @@
package electroblob.wizardry.entity.living;
import java.util.List;
import electroblob.wizardry.spell.Spell;
import java.util.List;
/**
* [NYI] Interface for entities that can select between spells based on their current circumstances, to be used in
* conjunction with {@link EntityAISelectSpell}.
@@ -1,24 +1,23 @@
package electroblob.wizardry.entity.living;
import java.util.List;
import javax.annotation.Nonnull;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.world.EnumDifficulty;
import javax.annotation.Nonnull;
import java.util.List;
/**
* Interface for entities that can cast spells. Mainly intended for use by wizard-type entities, but can be implemented
* by any subclass of EntityLiving. Designed to be as flexible as possible - ranging from the simplest use of giving an
* entity a specific spell as an attack, to a complex AI which selects different spell types depending on the situation.
* The only restriction is that the spells must be castable by NPCs.
* <p>
* <p></p>
* This is intended for entities that use {@link EntityAIAttackSpell}. If so, all the spell casting code (including
* packets) is handled by that class, and all the implementor needs to do is decide which spell(s) to select.
* <p>
* <p></p>
* This class also allows Wizardry to do all the syncing necessary for continuous spell casting. All the implementor
* needs to do is store the actual fields involved.
*/
@@ -1,28 +1,15 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.UUID;
import javax.annotation.Nullable;
import com.google.common.base.Predicate;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.IndirectMinionDamage;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.MinionDamage;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.*;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
@@ -36,28 +23,33 @@ import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.UUID;
/**
* Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 2.1, and this
* interface allows summoned creatures to extend vanilla (or indeed modded) entity classes, so
* <code>EntitySummonedZombie</code> now extends <code>EntityZombie</code>, for example. This change has two major
* benefits:
* <p>
* <p></p>
* - There is no longer any need for separate render classes, because summoned creatures are now instances of vanilla
* types. <i>You don't even need to assign a render class</i> because the supertype should already be assigned the
* correct one.<br>
* - Summoned creature classes are now much more robust when it comes to changes between Minecraft versions, since none
* of the vanilla code needs to be copied.
* <p>
* <p></p>
* <b>Summoned creatures that do not emulate vanilla entities do not directly implement this interface</b>. Instead,
* they should extend the abstract base implementation, {@link EntitySummonedCreature}.
* <p>
* <p></p>
* All damage dealt by ISummonedCreature instances is redirected via
* {@link ISummonedCreature#onLivingAttackEvent(net.minecraftforge.event.entity.living.LivingAttackEvent)
* {@link ISummonedCreature#onLivingAttackEvent(LivingAttackEvent)
* ISummonedCreature.onLivingAttackEvent(LivingAttackEvent)} and replaced by an instance of
* {@link electroblob.wizardry.util.IElementalDamage IElementalDamage} with the summoner of that creature as the source
* {@link IElementalDamage IElementalDamage} with the summoner of that creature as the source
* rather than the creature itself. This means that kills by summoned creatures register as kills for their owner,
* dropping xp and rare loot if that owner is a player.
* <p>
* <p></p>
* Though this system is a lot better than the previous system, <i>it is not a perfect solution</i>. The old
* EntitySummonedCreature class overrode some methods from Entity in order to add shared functionality, but this cannot
* be done with an interface. To get around this problem, this interface contains 5 delegate methods that do the same
@@ -67,12 +59,12 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
* work properly unless it is adhered to. <i>The position of the delegate method call is unimportant, but by convention
* it is usually at the start of the calling method, which avoids it being unintentionally skipped by a return
* statement (except for methods where the result of the delegate method should itself be returned).</i>
* <p>
* <p></p>
* It is recommended that when implementing this interface, you begin by copying {@link EntitySummonedCreature} to
* ensure all the relevant methods are duplicated. You can then change the superclass, override any additional methods
* and add functionality to any that are already overridden. You will always want to override the AI methods at the very
* least.
* <p>
* <p></p>
* Due to the limitations of interfaces, some methods that really ought to be protected are public. These are clearly
* marked as 'Internal, DO NOT CALL'. <b>Don't call them, only implement them.</b>
*
@@ -83,7 +75,7 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
* sacrifices have to be made when it comes to Java style - because adding on to a pre-existing program is not a good
* way of doing this sort of thing anyway, but we have no choice about that! */
@Mod.EventBusSubscriber
public interface ISummonedCreature extends IEntityAdditionalSpawnData {
public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOwnable {
// Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that.
String NAMEPLATE_TRANSLATION_KEY = "entity." + Wizardry.MODID + ":summonedcreature.nameplate";
@@ -101,43 +93,51 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
*/
int getLifetime();
/**
* Sets the WeakReference object which refers to the owner of this summoned creature. Internal, don't call unless
* you know what you are doing.
*/
void setCasterReference(WeakReference<EntityLivingBase> reference);
/** Internal, do not use. Implementing classes should implement this to set their owner UUID field. */
void setOwnerId(UUID uuid);
/**
* Returns a WeakReference object which refers to the owner of this summoned creature. Subclasses should store this
* as a private field. This may be null; as such it is preferable to use {@link ISummonedCreature#getCaster()} to
* get the caster object itself.
*/
/** Returns the UUID of the owner of this summoned creature, or null if it does not have an owner.
* Implementing classes should implement this to return their owner UUID field. */
@Nullable
WeakReference<EntityLivingBase> getCasterReference();
@Override
UUID getOwnerId(); // Only overridden because I wanted to add javadoc!
/** Internal, DO NOT CALL. */
void setCasterUUID(UUID uuid);
/** Internal, DO NOT CALL. This is for loading purposes only and is not usually synchronised. */
UUID getCasterUUID();
@Nullable
@Override
default Entity getOwner(){
return getCaster(); // Delegate to getCaster
}
/**
* Returns the EntityLivingBase that summoned this creature, or null if it no longer exists. Cases where the entity
* may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to
* another dimension, or this creature simply had no caster in the first place. <i>This is the correct method to use
* to get the owner of this summoned creature.
* another dimension, or this creature simply had no caster in the first place.
*/
@Nullable
default EntityLivingBase getCaster(){
return getCasterReference() == null ? null : getCasterReference().get();
default EntityLivingBase getCaster(){ // Kept despite the above method because it returns an EntityLivingBase
if(this instanceof Entity){ // Bit of a cheat but it saves having yet another method just to get the world
Entity entity = WizardryUtilities.getEntityByUUID(((Entity)this).world, getOwnerId());
if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen
Wizardry.logger.warn("{} has a non-living owner!", this);
return null;
}
return (EntityLivingBase)entity;
}else{
Wizardry.logger.warn("{} implements ISummonedCreature but is not an SoundLoopSpellEntity!", this.getClass());
return null;
}
}
/**
* Sets the EntityLivingBase that summoned this creature. <i>This is the correct method to use to set the owner of
* this summoned creature.
* Sets the EntityLivingBase that summoned this creature.
*/
default void setCaster(@Nullable EntityLivingBase caster){
setCasterReference(new WeakReference<EntityLivingBase>(caster));
setOwnerId(caster == null ? null : caster.getUniqueID());
}
// Miscellaneous
@@ -164,17 +164,56 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
default void readSpawnData(ByteBuf buffer){
int id = buffer.readInt();
// We're on the client side here, so we can safely use Minecraft.getMinecraft().world via proxies.
if(id > -1) setCasterReference(
new WeakReference<EntityLivingBase>((EntityLivingBase)Wizardry.proxy.getTheWorld().getEntityByID(id)));
if(id > -1){
Entity entity = Wizardry.proxy.getTheWorld().getEntityByID(id);
if(entity instanceof EntityLivingBase) setCaster((EntityLivingBase)entity);
else Wizardry.logger.warn("Received a spawn packet for entity {}, but no living entity matched the supplied ID", this);
}
setLifetime(buffer.readInt());
}
/**
* Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this creature as the
* attacker. Also allows implementors to override it if they wish to do so.
* Determines whether the given target is valid. Used by the default target selector (see
* {@link ISummonedCreature#getTargetSelector()}) and revenge targeting checks. This method is responsible for the
* ally designation system, default classes that may be targeted and the config whitelist/blacklist.
* Implementors may override this if they want to do something different or add their own checks.
* @see AllyDesignationSystem#isValidTarget(Entity, Entity)
*/
default boolean isValidTarget(Entity target){
return WizardryUtilities.isValidTarget(this.getCaster(), target);
// If the target is valid based on the ADS...
if(AllyDesignationSystem.isValidTarget(this.getCaster(), target)){
// ...and is a player, they can be attacked, since players can't be in the whitelist or the
// blacklist...
if(target instanceof EntityPlayer){
// ...unless the creature was summoned by a good wizard who the player has not angered.
if(getCaster() instanceof EntityWizard){
if(getCaster().getRevengeTarget() != target
&& ((EntityWizard)getCaster()).getAttackTarget() != target) {
return false;
}
}
return true;
}
// ...and is a mob, a summoned creature, a wizard...
if((target instanceof IMob || target instanceof ISummonedCreature
|| (target instanceof EntityWizard && !(getCaster() instanceof EntityWizard))
// ...or something that's attacking the owner...
|| (target instanceof EntityLiving && ((EntityLiving)target).getAttackTarget() == getCaster())
// ...or in the whitelist...
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist)
.contains(EntityList.getKey(target.getClass())))
// ...and isn't in the blacklist...
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
.contains(EntityList.getKey(target.getClass()))){
// ...it can be attacked.
return true;
}
}
return false;
}
/**
@@ -182,45 +221,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* possible for implementors to override this in order to do something special when selecting a target.
*/
default Predicate<Entity> getTargetSelector(){
return new Predicate<Entity>(){
public boolean apply(Entity entity){
// TODO: Backport invisibility check (also in wizards)
// If the target is valid and not invisible...
if(!entity.isInvisible() && isValidTarget(entity)){
// ... and is a player, they can be attacked, since players can't be in the whitelist or the
// blacklist ...
if(entity instanceof EntityPlayer){
// ... unless the creature was summoned by a good wizard who the player has not angered.
if(getCaster() instanceof EntityWizard){
if(((EntityWizard)getCaster()).getRevengeTarget() != entity
&& ((EntityWizard)getCaster()).getAttackTarget() != entity) {
return false;
}
}
return true;
}
// ... and is a mob, a summoned creature, a wizard ...
if((entity instanceof IMob || entity instanceof ISummonedCreature
|| (entity instanceof EntityWizard && !(getCaster() instanceof EntityWizard))
// ... or in the whitelist ...
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist)
.contains(EntityList.getKey(entity.getClass())))
// ... and isn't in the blacklist ...
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
.contains(EntityList.getKey(entity.getClass()))){
// ... it can be attacked.
return true;
}
}
return false;
}
};
return entity -> getCaster() == null ? entity instanceof EntityPlayer : !entity.isInvisible() && isValidTarget(entity);
}
/**
@@ -241,7 +242,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* Called from the event handler after the damage change is applied. Does nothing by default, but can be overridden
* to do something when a successful attack is made. This was added because the event-based damage source system can
* cause parts of attackEntityAsMob not to fire, since attackEntityFrom is intercepted and canceled.
* <p>
* <p></p>
* Usage examples: {@link EntitySilverfishMinion} uses this to summon more silverfish if the target is killed,
* {@link EntitySkeletonMinion} and {@link EntitySpiderMinion} use this to add potion effects to the target.
*/
@@ -266,7 +267,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* very little point in doing that since anything extra could just be added to readEntityFromNBT anyway.
*/
default void readNBTDelegate(NBTTagCompound tagcompound){
this.setCasterUUID(tagcompound.getUniqueId("casterUUID"));
this.setOwnerId(tagcompound.getUniqueId("casterUUID"));
this.setLifetime(tagcompound.getInteger("lifetime"));
}
@@ -275,8 +276,8 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* returns <b>true</b>.
*/
default boolean shouldRevengeTarget(EntityLivingBase entity){
// Allows the config to prevent minions from revenge-targeting their owners.
return entity != this.getCaster() || Wizardry.settings.minionRevengeTargeting;
// Allows the config to prevent minions from revenge-targeting their owners (or anything else, for that matter)
return Wizardry.settings.minionRevengeTargeting || isValidTarget(entity);
}
/**
@@ -286,22 +287,17 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
default void updateDelegate(){
if(!(this instanceof Entity))
throw new ClassCastException("Implementations of ISummonedCreature must extend Entity!");
throw new ClassCastException("Implementations of ISummonedCreature must extend SoundLoopSpellEntity!");
Entity thisEntity = ((Entity)this);
if(this.getCaster() == null && this.getCasterUUID() != null){
Entity entity = WizardryUtilities.getEntityByUUID(thisEntity.world, getCasterUUID());
if(entity instanceof EntityLivingBase){
this.setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)entity));
}
}
if(thisEntity.ticksExisted == 1){
this.onSpawn();
}
if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() != -1){
// For some reason Minecraft reads the entity from NBT just after the entity is created, so setting -1 as a
// default lifetime doesn't work. The easiest way around this is to use 0 - nobody's going to need it!
if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() > 0){
this.onDespawn();
thisEntity.setDead();
}
@@ -322,20 +318,20 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
ItemStack stack = player.getHeldItem(hand);
WizardData properties = WizardData.get(player);
WizardData data = WizardData.get(player);
// Selects one of the player's minions.
if(player.isSneaking() && stack.getItem() instanceof ItemWand){
if(player.isSneaking() && stack.getItem() instanceof ISpellCastingItem){
if(!player.world.isRemote && properties != null && this.getCaster() == player){
if(!player.world.isRemote && data != null && this.getCaster() == player){
if(properties.selectedMinion != null && properties.selectedMinion.get() == this){
if(data.selectedMinion != null && data.selectedMinion.get() == this){
// Deselects the selected minion if right-clicked again
properties.selectedMinion = null;
data.selectedMinion = null;
}else{
// Selects this minion
properties.selectedMinion = new WeakReference<ISummonedCreature>(this);
data.selectedMinion = new WeakReference<>(this);
}
properties.sync();
data.sync();
}
return true;
}
@@ -346,7 +342,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
// Damage system
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
static void onLivingAttackEvent(LivingAttackEvent event){
// Rather than bother overriding entire attack methods in ISummonedCreature implementations, it's easier (and
// more robust) to use LivingAttackEvent to modify the damage source.