package electroblob.wizardry.util; import electroblob.wizardry.Wizardry; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.spell.MindControl; import net.minecraft.entity.*; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; /** * Contains some useful static methods for interacting with the ally designation system. Also handles the friendly fire * setting. This was split off from {@code WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to * navigate. */ @Mod.EventBusSubscriber public final class AllyDesignationSystem { private AllyDesignationSystem(){} // No instances! /** Set of constants for each of the four friendly fire settings. */ public enum FriendlyFire { ALL("All", false, false), ONLY_PLAYERS("Only players", false, true), ONLY_OWNED("Only summoned/tamed creatures", true, false), NONE("None", true, true); /** Constant array storing the names of each of the constants, in the order they are declared. */ public static final String[] names; static { names = new String[values().length]; for(FriendlyFire setting : values()){ names[setting.ordinal()] = setting.name; } } /** The readable name for this friendly fire setting that will be displayed on the button in the config GUI. */ public final String name; public final boolean blockPlayers; public final boolean blockOwned; FriendlyFire(String name, boolean blockPlayers, boolean blockOwned){ this.name = name; this.blockPlayers = blockPlayers; this.blockOwned = blockOwned; } /** * Gets a friendly fire setting from its string name (ignoring case), or ALL if the given name is not a valid * setting. */ public static FriendlyFire fromName(String name){ for(FriendlyFire setting : values()){ if(setting.name.equalsIgnoreCase(name)) return setting; } Wizardry.logger.info("Invalid string for the friendly fire setting. Using default (all) instead."); return ALL; } } /** * Returns whether the given target can be attacked by the given attacker. It is up to the caller of this method to * work out what this means; it doesn't necessarily mean the target is completely immune (for example, revenge * targeting might reasonably bypass this). This method is intended for use where the damage is indirect and/or * unavoidable; direct attacks should not check this method. Currently this means the following situations check * this method: *

* - AI targeting for summoned creatures
* - AI targeting for mind-controlled creatures
* - Constructs with an area of effect
* - Instantaneous spells with an area of effect around the caster (e.g. forest's curse, thunderstorm)
* - Any lightning chaining effects
* - Any projectiles which seek targets *

* Also note that the friendly fire option is dealt with in the event handler. This method acts as a sort of wrapper * for all the AllyDesignationSystem stuff in {@link WizardData}; more details about the ally designation system can be found there. * * @param attacker The entity that cast the spell originally * @param target The entity being attacked * * @return False under any of the following circumstances, true otherwise: *

* - The target is null *

* - The target is the attacker (this isn't as stupid as it sounds - anything with an AoE might cause this * to be true, as can summoned creatures) *

* - The target and the attacker are both players and the target is an ally of the attacker (but the * attacker need not be an ally of the target) *

* - The target is a creature that was summoned/controlled by the attacker or by an ally of the attacker. *

* - The target is a creature that was tamed by the attacker or by an ally of the attacker * (see {@link net.minecraft.entity.IEntityOwnable}). *

* As of wizardry 4.1.2, this method now returns true instead of false if the attacker is null. This * is because in the vast majority of cases, it makes more sense this way: if a construct has no caster, it * should affect all entities; if a minion has no caster it should target all entities; etc. */ public static boolean isValidTarget(Entity attacker, Entity target){ // Owned entities inherit their owner's allies if(attacker instanceof IEntityOwnable && !isValidTarget(((IEntityOwnable)attacker).getOwner(), target)) return false; // Always return false if the target is null if(target == null) return false; // Always return true if the attacker is null - this must be after the target null check! if(attacker == null) return true; // Tests whether the target is the attacker if(target == attacker) return false; // I really shouldn't need to do this, but fake players seem to break stuff... if(target instanceof FakePlayer) return false; // Use a positive check for these rather than a negative check for monsters, because we only want mobs // that are definitely passive if(Wizardry.settings.passiveMobsAreAllies && (target.isCreatureType(EnumCreatureType.AMBIENT, false) || target.isCreatureType(EnumCreatureType.CREATURE, false) || target.isCreatureType(EnumCreatureType.WATER_CREATURE, false))){ return false; } // Tests whether the target is a creature that was summoned by the attacker // if(target instanceof ISummonedCreature && ((ISummonedCreature)target).getCaster() == attacker){ // return false; // } // Tests whether the target is a creature that was summoned/tamed (or is otherwise owned) by the attacker if(target instanceof IEntityOwnable && ((IEntityOwnable)target).getOwner() == attacker){ return false; } // Tests whether the target is a creature that was summoned/tamed (or is otherwise owned) by the attacker if(target instanceof IEntityOwnable && attacker instanceof EntityLiving && !(((EntityLiving)attacker).getRevengeTarget() == ((IEntityOwnable)target).getOwner() || ((EntityLiving)attacker).getAttackTarget() == ((IEntityOwnable)target).getOwner())){ return false; } // Tests whether the target is a creature that was mind controlled by the attacker if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){ NBTTagCompound entityNBT = target.getEntityData(); if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){ if(attacker == EntityUtils.getEntityByUUID(target.world, entityNBT.getUniqueId(MindControl.NBT_KEY))){ return false; } } } // Ally section if(attacker instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker) != null){ if(target instanceof EntityPlayer){ // Tests whether the target is an ally of the attacker if(WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)target)){ return false; } // }else if(target instanceof ISummonedCreature){ // // Tests whether the target is a creature that was summoned by an ally of the attacker // if(((ISummonedCreature)target).getCaster() instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker) // .isPlayerAlly((EntityPlayer)((ISummonedCreature)target).getCaster())){ // return false; // } }else if(target instanceof IEntityOwnable){ // Tests whether the target is a creature that was summoned/tamed by an ally of the attacker if(isOwnerAlly((EntityPlayer)attacker, (IEntityOwnable)target)) return false; }else if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){ // Tests whether the target is a creature that was mind controlled by an ally of the attacker NBTTagCompound entityNBT = target.getEntityData(); if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY)){ Entity controller = EntityUtils.getEntityByUUID(target.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); if(controller instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)controller)){ return false; } } } } return true; } /** Umbrella method that covers both {@link AllyDesignationSystem#isPlayerAlly(EntityPlayer, EntityPlayer)} and * {@link AllyDesignationSystem#isOwnerAlly(EntityPlayer, IEntityOwnable)}, returning true if the second entity is * either owned by the first entity, an ally of the first entity, or owned by an ally of the first entity. This is * generally used to determine targets for healing or other group buffs. */ public static boolean isAllied(EntityLivingBase allyOf, EntityLivingBase possibleAlly){ // Owned entities inherit their owner's allies if(allyOf instanceof IEntityOwnable){ Entity owner = ((IEntityOwnable)allyOf).getOwner(); if(owner instanceof EntityLivingBase && (owner == possibleAlly || isAllied((EntityLivingBase)owner, possibleAlly))) return true; } if(allyOf instanceof EntityPlayer && possibleAlly instanceof EntityPlayer && isPlayerAlly((EntityPlayer)allyOf, (EntityPlayer)possibleAlly)){ return true; } if(possibleAlly instanceof IEntityOwnable){ IEntityOwnable pet = (IEntityOwnable)possibleAlly; if(pet.getOwner() == allyOf) return true; if(allyOf instanceof EntityPlayer && isOwnerAlly((EntityPlayer)allyOf, pet)) return true; } // Check if the possibleAlly is mind controlled by the allyOf entity if(possibleAlly instanceof EntityLiving && possibleAlly.isPotionActive(WizardryPotions.mind_control)){ NBTTagCompound entityNBT = possibleAlly.getEntityData(); if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){ Entity controller = EntityUtils.getEntityByUUID(possibleAlly.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); return controller == allyOf; } } return false; } /** Helper method for testing if the second player is an ally of the first player. Makes the code neater. * @see AllyDesignationSystem#isOwnerAlly(EntityPlayer, IEntityOwnable) */ public static boolean isPlayerAlly(EntityPlayer allyOf, EntityPlayer possibleAlly){ WizardData data = WizardData.get(allyOf); return data != null && data.isPlayerAlly(possibleAlly); } /** Helper method for testing if the given {@link net.minecraft.entity.IEntityOwnable}'s owner is an ally of the * given player. This works even when the owner is not logged in, though it may not correctly respect teams when * that is the case. */ public static boolean isOwnerAlly(EntityPlayer allyOf, IEntityOwnable ownable){ WizardData data = WizardData.get(allyOf); if(data == null) return false; Entity owner = ownable.getOwner(); return owner instanceof EntityPlayer ? data.isPlayerAlly((EntityPlayer)owner) : data.isPlayerAlly(ownable.getOwnerId()); } @SubscribeEvent public static void onLivingAttackEvent(LivingAttackEvent event){ if(event.getSource() != null && event.getSource().getTrueSource() instanceof EntityPlayer && event.getSource() instanceof IElementalDamage){ if(event.getEntity() instanceof EntityPlayer){ // Prevents any magic damage to allied players if friendly fire is disabled for players if(Wizardry.settings.friendlyFire.blockPlayers && isPlayerAlly((EntityPlayer)event.getSource().getTrueSource(), (EntityPlayer)event.getEntity())){ event.setCanceled(true); } }else{ // Prevents any magic damage to entities owned by allied players if friendly fire is disabled for owned creatures // Since we're dealing with players separately we might as well just use isAllied if(Wizardry.settings.friendlyFire.blockOwned && isAllied((EntityPlayer)event.getSource().getTrueSource(), event.getEntityLiving())){ event.setCanceled(true); } } } } }