Miscellaneous setup
This commit is contained in:
@@ -3,16 +3,18 @@ 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;
|
||||
import electroblob.wizardry.packet.WizardryPacketHandler;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.ai.EntityAIBase;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
|
||||
|
||||
@@ -20,192 +22,217 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
|
||||
* and the attack cooldown. Also provides an automatic implementation of continuous spell casting using the methods
|
||||
* specified in {@code ISpellCaster}; all the entity class needs to do is implement those methods. */
|
||||
public class EntityAIAttackSpell extends EntityAIBase {
|
||||
|
||||
/** The entity the AI instance has been applied to. */
|
||||
private final EntityLiving attacker;
|
||||
/** The entity the AI instance has been applied to, but as an ISpellCaster. */
|
||||
private final ISpellCaster caster;
|
||||
/** The tagret to be attacked. */
|
||||
private EntityLivingBase target;
|
||||
/** Decremented each tick while greater than 0. When a spell is cast, this is set to that spell's cooldown plus
|
||||
* the base cooldown. */
|
||||
private int cooldown;
|
||||
/** The number of ticks between the entity finding a new target and when it first starts attacking, and also the
|
||||
* amount that is added to the spell's cooldown between casting spells. */
|
||||
private final int baseCooldown;
|
||||
/** Decremented each tick while greater than 0. When a continuous spell is first cast, this is set to
|
||||
* the value of {@link EntityAIAttackSpell#continuousSpellDuration}. */
|
||||
// I think that in this case this is only necessary on the server side. If any inconsistent behaviour
|
||||
// occurs, look into syncing this as well.
|
||||
private int continuousSpellTimer;
|
||||
/** The number of ticks that continuous spells will be cast for before cooling down. */
|
||||
private final int continuousSpellDuration;
|
||||
/** The speed that the entity should move when attacking. Only used when passed into the navigator. */
|
||||
private final double speed;
|
||||
private int seeTime;
|
||||
private final float maxAttackDistance;
|
||||
|
||||
/**
|
||||
* Creates a new spell attack AI with the given parameters.
|
||||
* @param attacker The entity that that uses this AI.
|
||||
* @param speed The speed that the entity should move when attacking. Only used when passed into the navigator.
|
||||
* @param maxDistance The maximum distance the entity should be from its target.
|
||||
* @param baseCooldown The number of ticks between the entity finding a new target and when it first starts attacking,
|
||||
* and also the amount that is added to the cooldown of the spell that has just been cast.
|
||||
* @param continuousSpellDuration The number of ticks that continuous spells will be cast for before cooling down.
|
||||
*/
|
||||
public EntityAIAttackSpell(ISpellCaster attacker, double speed, float maxDistance, int baseCooldown, int continuousSpellDuration){
|
||||
|
||||
this.cooldown = -1;
|
||||
|
||||
if(!(attacker instanceof EntityLiving)){
|
||||
throw new IllegalArgumentException("Tried to create an EntityAICastSpell for an entity that isn't an EntityLiving");
|
||||
}else{
|
||||
this.caster = attacker;
|
||||
this.attacker = (EntityLiving)attacker;
|
||||
this.baseCooldown = baseCooldown;
|
||||
this.continuousSpellDuration = continuousSpellDuration;
|
||||
this.speed = speed;
|
||||
this.maxAttackDistance = maxDistance * maxDistance;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
}
|
||||
/** The entity the AI instance has been applied to. */
|
||||
private final EntityLiving attacker;
|
||||
/** The entity the AI instance has been applied to, but as an ISpellCaster. */
|
||||
private final ISpellCaster caster;
|
||||
/** The tagret to be attacked. */
|
||||
private EntityLivingBase target;
|
||||
/** Decremented each tick while greater than 0. When a spell is cast, this is set to that spell's cooldown plus
|
||||
* the base cooldown. */
|
||||
private int cooldown;
|
||||
/** The number of ticks between the entity finding a new target and when it first starts attacking, and also the
|
||||
* amount that is added to the spell's cooldown between casting spells. */
|
||||
private final int baseCooldown;
|
||||
/** Decremented each tick while greater than 0. When a continuous spell is first cast, this is set to
|
||||
* the value of {@link EntityAIAttackSpell#continuousSpellDuration}. */
|
||||
// I think that in this case this is only necessary on the server side. If any inconsistent behaviour
|
||||
// occurs, look into syncing this as well.
|
||||
private int continuousSpellTimer;
|
||||
/** The number of ticks that continuous spells will be cast for before cooling down. */
|
||||
private final int continuousSpellDuration;
|
||||
/** The speed that the entity should move when attacking. Only used when passed into the navigator. */
|
||||
private final double speed;
|
||||
private int seeTime;
|
||||
private final float maxAttackDistance;
|
||||
|
||||
@Override
|
||||
public boolean shouldExecute(){
|
||||
|
||||
EntityLivingBase entitylivingbase = this.attacker.getAttackTarget();
|
||||
/**
|
||||
* Creates a new spell attack AI with the given parameters.
|
||||
* @param attacker The entity that that uses this AI.
|
||||
* @param speed The speed that the entity should move when attacking. Only used when passed into the navigator.
|
||||
* @param maxDistance The maximum distance the entity should be from its target.
|
||||
* @param baseCooldown The number of ticks between the entity finding a new target and when it first starts attacking,
|
||||
* and also the amount that is added to the cooldown of the spell that has just been cast.
|
||||
* @param continuousSpellDuration The number of ticks that continuous spells will be cast for before cooling down.
|
||||
*/
|
||||
public EntityAIAttackSpell(ISpellCaster attacker, double speed, float maxDistance, int baseCooldown, int continuousSpellDuration){
|
||||
|
||||
if(entitylivingbase == null){
|
||||
return false;
|
||||
}else{
|
||||
this.target = entitylivingbase;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.cooldown = -1;
|
||||
|
||||
if(!(attacker instanceof EntityLiving)){
|
||||
throw new IllegalArgumentException("Tried to create an EntityAICastSpell for an entity that isn't an EntityLiving");
|
||||
}else{
|
||||
this.caster = attacker;
|
||||
this.attacker = (EntityLiving)attacker;
|
||||
this.baseCooldown = baseCooldown;
|
||||
this.continuousSpellDuration = continuousSpellDuration;
|
||||
this.speed = speed;
|
||||
this.maxAttackDistance = maxDistance * maxDistance;
|
||||
this.setMutexBits(3);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueExecuting(){
|
||||
return this.shouldExecute() || !this.attacker.getNavigator().noPath();
|
||||
}
|
||||
public boolean shouldExecute(){
|
||||
|
||||
@Override
|
||||
public void resetTask(){
|
||||
this.target = null;
|
||||
this.seeTime = 0;
|
||||
this.cooldown = -1;
|
||||
this.setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.continuousSpellTimer = 0;
|
||||
}
|
||||
|
||||
private void setContinuousSpellAndNotify(Spell spell, SpellModifiers modifiers){
|
||||
caster.setContinuousSpell(spell);
|
||||
WizardryPacketHandler.net.sendToAllAround(new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target == null ? -1 : target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers),
|
||||
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
|
||||
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
|
||||
}
|
||||
EntityLivingBase entitylivingbase = this.attacker.getAttackTarget();
|
||||
|
||||
@Override
|
||||
public void updateTask(){
|
||||
|
||||
// Only executed server side.
|
||||
|
||||
double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.getEntityBoundingBox().minY, this.target.posZ);
|
||||
boolean targetIsVisible = this.attacker.getEntitySenses().canSee(this.target);
|
||||
if(entitylivingbase == null){
|
||||
return false;
|
||||
}else{
|
||||
this.target = entitylivingbase;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(targetIsVisible){
|
||||
++this.seeTime;
|
||||
}else{
|
||||
this.seeTime = 0;
|
||||
}
|
||||
@Override
|
||||
public boolean continueExecuting(){
|
||||
return this.shouldExecute() || !this.attacker.getNavigator().noPath();
|
||||
}
|
||||
|
||||
if(distanceSq <= (double)this.maxAttackDistance && this.seeTime >= 20){
|
||||
this.attacker.getNavigator().clearPathEntity();
|
||||
}else{
|
||||
this.attacker.getNavigator().tryMoveToEntityLiving(this.target, this.speed);
|
||||
}
|
||||
@Override
|
||||
public void resetTask(){
|
||||
this.target = null;
|
||||
this.seeTime = 0;
|
||||
this.cooldown = -1;
|
||||
this.setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.continuousSpellTimer = 0;
|
||||
}
|
||||
|
||||
this.attacker.getLookHelper().setLookPositionWithEntity(this.target, 30.0F, 30.0F);
|
||||
private void setContinuousSpellAndNotify(Spell spell, SpellModifiers modifiers){
|
||||
caster.setContinuousSpell(spell);
|
||||
WizardryPacketHandler.net.sendToAllAround(new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target == null ? -1 : target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers),
|
||||
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
|
||||
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
|
||||
}
|
||||
|
||||
if(this.continuousSpellTimer > 0){
|
||||
|
||||
this.continuousSpellTimer--;
|
||||
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
|
||||
|| !caster.getContinuousSpell().cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND,
|
||||
this.continuousSpellDuration - this.continuousSpellTimer, target, caster.getModifiers())
|
||||
|| this.continuousSpellTimer == 0){
|
||||
// If the spell no longer succeeds, the target goes out of range or sight, or the time has elapsed,
|
||||
// reset the continuous spell timer and start the cooldown.
|
||||
this.continuousSpellTimer = 0;
|
||||
setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.cooldown = this.baseCooldown;
|
||||
return;
|
||||
}
|
||||
|
||||
}else if(--this.cooldown == 0){
|
||||
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!attacker.isPotionActive(WizardryPotions.arcane_jammer)){
|
||||
|
||||
double dx = target.posX - attacker.posX;
|
||||
double dz = target.posZ - attacker.posZ;
|
||||
@Override
|
||||
public void updateTask(){
|
||||
|
||||
List<Spell> spells = new ArrayList<Spell>(caster.getSpells());
|
||||
|
||||
if(spells.size() > 0){
|
||||
// Only executed server side.
|
||||
|
||||
if(!attacker.worldObj.isRemote){
|
||||
double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.getEntityBoundingBox().minY, this.target.posZ);
|
||||
boolean targetIsVisible = this.attacker.getEntitySenses().canSee(this.target);
|
||||
|
||||
// New way of choosing a spell; keeps trying until one works or all have been tried
|
||||
if(targetIsVisible){
|
||||
++this.seeTime;
|
||||
}else{
|
||||
this.seeTime = 0;
|
||||
}
|
||||
|
||||
Spell spell;
|
||||
if(distanceSq <= (double)this.maxAttackDistance && this.seeTime >= 20){
|
||||
this.attacker.getNavigator().clearPathEntity();
|
||||
}else{
|
||||
this.attacker.getNavigator().tryMoveToEntityLiving(this.target, this.speed);
|
||||
}
|
||||
|
||||
while(!spells.isEmpty()){
|
||||
this.attacker.getLookHelper().setLookPositionWithEntity(this.target, 30.0F, 30.0F);
|
||||
|
||||
spell = spells.get(attacker.worldObj.rand.nextInt(spells.size()));
|
||||
|
||||
SpellModifiers modifiers = caster.getModifiers();
|
||||
if(this.continuousSpellTimer > 0){
|
||||
|
||||
if(spell != null && spell.cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){
|
||||
this.continuousSpellTimer--;
|
||||
|
||||
// If the target goes out of range or out of sight...
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
|
||||
// ...or the spell is cancelled via events...
|
||||
|| MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(attacker, caster.getContinuousSpell(),
|
||||
caster.getModifiers(), Source.NPC, this.continuousSpellDuration - this.continuousSpellTimer))
|
||||
// ...or the spell no longer succeeds...
|
||||
|| !caster.getContinuousSpell().cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND,
|
||||
this.continuousSpellDuration - this.continuousSpellTimer, target, caster.getModifiers())
|
||||
// ...or the time has elapsed...
|
||||
|| this.continuousSpellTimer == 0){
|
||||
|
||||
// ...reset the continuous spell timer and start the cooldown.
|
||||
this.continuousSpellTimer = 0;
|
||||
setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
|
||||
this.cooldown = this.baseCooldown;
|
||||
return;
|
||||
|
||||
}else if(this.continuousSpellDuration - this.continuousSpellTimer == 1){
|
||||
// On the first tick, if the spell did succeed, fire SpellCastEvent.Post.
|
||||
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, caster.getContinuousSpell(),
|
||||
caster.getModifiers(), Source.NPC));
|
||||
}
|
||||
|
||||
if(spell.isContinuous){
|
||||
// -1 because the spell has been cast once already!
|
||||
this.continuousSpellTimer = this.continuousSpellDuration - 1;
|
||||
setContinuousSpellAndNotify(spell, modifiers);
|
||||
|
||||
}else{
|
||||
// For now, the cooldown is just added to the constant base cooldown. I think this
|
||||
// is a reasonable way of doing things; it's certainly better than before.
|
||||
this.cooldown = this.baseCooldown + spell.cooldown;
|
||||
|
||||
if(spell.doesSpellRequirePacket()){
|
||||
// Sends a packet to all players in dimension to tell them to spawn particles.
|
||||
IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers);
|
||||
WizardryPacketHandler.net.sendToDimension(msg, attacker.worldObj.provider.getDimension());
|
||||
}
|
||||
}
|
||||
}else if(--this.cooldown == 0){
|
||||
|
||||
attacker.rotationYaw = (float)(Math.atan2(dz, dx) * 180.0D / Math.PI) - 90.0F;
|
||||
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible){
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
double dx = target.posX - attacker.posX;
|
||||
double dz = target.posZ - attacker.posZ;
|
||||
|
||||
}else{
|
||||
spells.remove(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else if(this.cooldown < 0){
|
||||
// This should only be reached when the entity first starts attacking. Stops it attacking instantly.
|
||||
this.cooldown = this.baseCooldown;
|
||||
}
|
||||
}
|
||||
List<Spell> spells = new ArrayList<Spell>(caster.getSpells());
|
||||
|
||||
if(spells.size() > 0){
|
||||
|
||||
if(!attacker.worldObj.isRemote){
|
||||
|
||||
// New way of choosing a spell; keeps trying until one works or all have been tried
|
||||
|
||||
Spell spell;
|
||||
|
||||
while(!spells.isEmpty()){
|
||||
|
||||
spell = spells.get(attacker.worldObj.rand.nextInt(spells.size()));
|
||||
|
||||
SpellModifiers modifiers = caster.getModifiers();
|
||||
|
||||
if(spell != null && attemptCastSpell(spell, modifiers)){
|
||||
// The spell worked, so we're done!
|
||||
attacker.rotationYaw = (float)(Math.atan2(dz, dx) * 180.0D / Math.PI) - 90.0F;
|
||||
return;
|
||||
}else{
|
||||
spells.remove(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else if(this.cooldown < 0){
|
||||
// This should only be reached when the entity first starts attacking. Stops it attacking instantly.
|
||||
this.cooldown = this.baseCooldown;
|
||||
}
|
||||
}
|
||||
|
||||
/** Attempts to cast the given spell (including event firing) and returns true if it succeeded. */
|
||||
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))){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(spell.cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){
|
||||
|
||||
if(spell.isContinuous){
|
||||
// -1 because the spell has been cast once already!
|
||||
this.continuousSpellTimer = this.continuousSpellDuration - 1;
|
||||
setContinuousSpellAndNotify(spell, modifiers);
|
||||
|
||||
}else{
|
||||
|
||||
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, spell, modifiers, Source.NPC));
|
||||
|
||||
// For now, the cooldown is just added to the constant base cooldown. I think this
|
||||
// is a reasonable way of doing things; it's certainly better than before.
|
||||
this.cooldown = this.baseCooldown + spell.cooldown;
|
||||
|
||||
if(spell.doesSpellRequirePacket()){
|
||||
// Sends a packet to all players in dimension to tell them to spawn particles.
|
||||
IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(),
|
||||
target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers);
|
||||
WizardryPacketHandler.net.sendToDimension(msg, attacker.worldObj.provider.getDimension());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
|
||||
protected Predicate<Entity> targetSelector;
|
||||
|
||||
/** Data parameter for the cooldown time for wizards healing themselves. */
|
||||
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
|
||||
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityEvilWizard.class, DataSerializers.VARINT);
|
||||
/** Data parameter for the wizard's element. */
|
||||
private static final DataParameter<Integer> ELEMENT = EntityDataManager.createKey(EntityEvilWizard.class, DataSerializers.VARINT);
|
||||
/** The resource location for the evil wizard's loot table. */
|
||||
|
||||
@@ -278,6 +278,7 @@ public class EntityIceWraith extends EntityBlazeMinion {
|
||||
if(this.attackStep > 1){
|
||||
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
|
||||
Spells.ice_shard.cast(this.blaze.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
|
||||
// TODO: Decide if an event should be fired here. I'm guessing no.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ 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.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
|
||||
// TODO: Decide if an event should be fired here. I'm guessing no.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,13 +69,18 @@ import net.minecraft.village.MerchantRecipe;
|
||||
import net.minecraft.village.MerchantRecipeList;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
@Mod.EventBusSubscriber
|
||||
public class EntityWizard extends EntityVillager implements ISpellCaster, IEntityAdditionalSpawnData {
|
||||
|
||||
/*
|
||||
@@ -779,6 +784,25 @@ public class EntityWizard extends EntityVillager implements ISpellCaster, IEntit
|
||||
// Uses .equals() rather than == so this will work fine.
|
||||
return this.towerBlocks.contains(pos);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onBlockBreakEvent(BlockEvent.BreakEvent event){
|
||||
// Makes wizards angry if a player breaks a block in their tower
|
||||
if(!(event.getPlayer() instanceof FakePlayer)){
|
||||
|
||||
List<EntityWizard> wizards = WizardryUtilities.getEntitiesWithinRadius(64, event.getPos().getX(),
|
||||
event.getPos().getY(), event.getPos().getZ(), event.getWorld(), EntityWizard.class);
|
||||
|
||||
if(!wizards.isEmpty()){
|
||||
for(EntityWizard wizard : wizards){
|
||||
if(wizard.isBlockPartOfTower(event.getPos())){
|
||||
wizard.setRevengeTarget(event.getPlayer());
|
||||
event.getPlayer().addStat(WizardryAchievements.anger_wizard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EntityVillager overrides (that don't add features)
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.WizardryEventHandler;
|
||||
import electroblob.wizardry.item.ItemWand;
|
||||
import electroblob.wizardry.util.IElementalDamage;
|
||||
import electroblob.wizardry.util.IndirectMinionDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.MinionDamage;
|
||||
import electroblob.wizardry.util.WizardryParticleType;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -23,10 +27,16 @@ import net.minecraft.entity.monster.IMob;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EntityDamageSource;
|
||||
import net.minecraft.util.EntityDamageSourceIndirect;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraftforge.event.entity.living.LivingAttackEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
|
||||
/** Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 1.2, and this
|
||||
/** Interface 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>
|
||||
@@ -59,13 +69,14 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
* <p>
|
||||
* Due to the limitations of interfaces, some methods that really ought to be protected are public. These are clearly
|
||||
* marked as 'Internal, DO NOT CALL'. <b>Don't call them, only implement them.</b>
|
||||
* @since Wizardry 1.2
|
||||
* @since Wizardry 2.1
|
||||
* @author Electroblob */
|
||||
/*
|
||||
* Quite honestly, this is not what default methods are really for. However, this is modding, and in modding some
|
||||
* sacrifices have to be made when it comes to Java style - because adding on to a pre-existing program is not a
|
||||
* good way of doing this sort of thing anyway, but we have no choice about that!
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
|
||||
// Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that.
|
||||
@@ -78,7 +89,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
|
||||
/** Returns the lifetime of the summoned creature in ticks. Allows primarily for duration multiplier support, but
|
||||
* also for example the skeleton legion spell which lasts for 60 seconds instead of the usual 30. Syncing and saving
|
||||
* is done automatically. As of Wizardry 1.2, despawning is handled in ISummonedCreature; see
|
||||
* is done automatically. As of Wizardry 2.1, despawning is handled in ISummonedCreature; see
|
||||
* {@link ISummonedCreature#onDespawn()} for details. */
|
||||
int getLifetime();
|
||||
|
||||
@@ -274,5 +285,49 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Damage system
|
||||
|
||||
@SubscribeEvent
|
||||
public 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.
|
||||
if(event.getSource().getEntity() instanceof ISummonedCreature){
|
||||
|
||||
EntityLivingBase summoner = ((ISummonedCreature)event.getSource().getEntity()).getCaster();
|
||||
|
||||
if(summoner != null){
|
||||
|
||||
event.setCanceled(true);
|
||||
DamageSource newSource = event.getSource();
|
||||
// Copies over the original DamageType if appropriate.
|
||||
DamageType type = event.getSource() instanceof IElementalDamage ? ((IElementalDamage)event.getSource()).getType() : DamageType.MAGIC;
|
||||
// Copies over the original isRetaliatory flag if appropriate.
|
||||
boolean isRetaliatory = event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory();
|
||||
|
||||
// All summoned creatures are classified as magic, so it makes sense to do it this way.
|
||||
if(event.getSource() instanceof EntityDamageSourceIndirect){
|
||||
newSource = new IndirectMinionDamage(event.getSource().damageType, event.getSource().getSourceOfDamage(), event.getSource().getEntity(), summoner, type, isRetaliatory);
|
||||
}else if(event.getSource() instanceof EntityDamageSource){
|
||||
// Name is copied over so it uses the appropriate vanilla death message
|
||||
newSource = new MinionDamage(event.getSource().damageType, event.getSource().getEntity(), summoner, type, isRetaliatory);
|
||||
}
|
||||
|
||||
// Copy over any relevant 'attributes' the original DamageSource might have had.
|
||||
if(event.getSource().isExplosion()) newSource.setExplosion();
|
||||
if(event.getSource().isFireDamage()) newSource.setFireDamage();
|
||||
if(event.getSource().isProjectile()) newSource.setProjectile();
|
||||
|
||||
// For some reason Minecraft calculates knockback relative to DamageSource#getEntity. In vanilla this
|
||||
// is unnoticeable, but it looks a bit weird with summoned creatures involved - so this fixes that.
|
||||
if(WizardryUtilities.attackEntityWithoutKnockback(event.getEntity(), newSource, event.getAmount())){
|
||||
WizardryUtilities.applyStandardKnockback(event.getSource().getEntity(), event.getEntityLiving());
|
||||
((ISummonedCreature)event.getSource().getEntity()).onSuccessfulAttack(event.getEntityLiving());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user