diff --git a/src/main/java/pvpmode/api/common/utils/PvPCommonUtils.java b/src/main/java/pvpmode/api/common/utils/PvPCommonUtils.java index 4749aef..8e34052 100644 --- a/src/main/java/pvpmode/api/common/utils/PvPCommonUtils.java +++ b/src/main/java/pvpmode/api/common/utils/PvPCommonUtils.java @@ -8,9 +8,13 @@ import org.apache.commons.io.IOUtils; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.event.entity.living.LivingAttackEvent; import pvpmode.PvPMode; import pvpmode.api.common.SimpleLogger; +import sun.reflect.CallerSensitive; +import sun.reflect.Reflection; public class PvPCommonUtils { @@ -426,6 +430,33 @@ public static Collection createInstances ( return createInstances (classes, null, null); } + /** + * Returns whether this is a bugged duplicate post of the LivingAttackEvent. + * Forge posts this event twice, once in EntityPlayer, and once in + * EntityLivingBase, so this will tell if an EntityPlayer got hurt but the + * event was posted as an EntityLivingBase. This should only be called from + * an {@link SubscribeEvent @SubscribeEvent} method with the event type of + * {@link LivingAttackEvent}. + * + * @param event + * The event to be checked + * @return If an EntityPlayer got hurt but the event was posted as an + * EntityLivingBase + */ + @SuppressWarnings("deprecation") + @CallerSensitive + public static boolean isCurrentAttackDuplicate (LivingAttackEvent event) + { + if (! (event.entityLiving instanceof EntityPlayer)) + return false; + for (StackTraceElement element : Thread.currentThread ().getStackTrace ()) + { + if (Reflection.getCallerClass (7) == EntityLivingBase.class) + return true; + } + return false; + } + /** * Returns a {@link SimpleLogger} instance assigned to the specified name. * diff --git a/src/main/java/pvpmode/api/server/compatibility/events/OnPvPEvent.java b/src/main/java/pvpmode/api/server/compatibility/events/OnPvPEvent.java new file mode 100644 index 0000000..6d22ec1 --- /dev/null +++ b/src/main/java/pvpmode/api/server/compatibility/events/OnPvPEvent.java @@ -0,0 +1,83 @@ +package pvpmode.api.server.compatibility.events; + +import cpw.mods.fml.common.eventhandler.*; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.DamageSource; +import pvpmode.api.common.EnumPvPMode; + +/** + * Fired when a player attacks another player. + * + * @author Vinyarion + * + */ +@Cancelable +public class OnPvPEvent extends Event +{ + + private final EntityPlayer attacker; + private final EnumPvPMode attackerMode; + private final EntityPlayer victim; + private final EnumPvPMode victimMode; + private final float damageAmount; + private final DamageSource source; + + public OnPvPEvent (EntityPlayer attacker, EnumPvPMode attackerMode, EntityPlayer victim, EnumPvPMode victimMode, float damageAmount, DamageSource source) + { + this.attacker = attacker; + this.attackerMode = attackerMode; + this.victim = victim; + this.victimMode = victimMode; + this.damageAmount = damageAmount; + this.source = source; + } + + /** + * Returns the attacker + */ + public EntityPlayer getAttacker () + { + return attacker; + } + + /** + * Returns the attacker's mode + */ + public EnumPvPMode getAttackerMode () + { + return attackerMode; + } + + /** + * Returns the victim + */ + public EntityPlayer getVictim () + { + return victim; + } + + /** + * Returns the victim's mode + */ + public EnumPvPMode getVictimMode () + { + return victimMode; + } + + /** + * Returns the amount of damage to be dealt + */ + public float getDamageAmount () + { + return damageAmount; + } + + /** + * Returns the damage source + */ + public DamageSource getSource () + { + return source; + } + +} diff --git a/src/main/java/pvpmode/internal/server/PvPServerEventHandler.java b/src/main/java/pvpmode/internal/server/PvPServerEventHandler.java index 4bd4cc3..1cbb7d3 100644 --- a/src/main/java/pvpmode/internal/server/PvPServerEventHandler.java +++ b/src/main/java/pvpmode/internal/server/PvPServerEventHandler.java @@ -20,6 +20,7 @@ import net.minecraftforge.event.entity.player.PlayerDropsEvent; import pvpmode.PvPMode; import pvpmode.api.common.EnumPvPMode; +import pvpmode.api.common.utils.PvPCommonUtils; import pvpmode.api.common.version.*; import pvpmode.api.server.PvPData; import pvpmode.api.server.compatibility.events.*; @@ -43,11 +44,17 @@ public PvPServerEventHandler () /** * Cancels combat events associated with PvP-disabled players. Note that this - * function will be invoked twice per attack - this is because of a Forge bug. + * function will be invoked twice per attack - this is because of a Forge bug, + * but the {@link PvPCommonUtils#isCurrentAttackDuplicate()} call checks and + * returns if this call is a duplicate */ @SubscribeEvent public void interceptPvP (LivingAttackEvent event) { + // This alleviates duplicate entries. + if (PvPCommonUtils.isCurrentAttackDuplicate (event)) + return; + EntityPlayerMP attacker = PvPServerUtils.getMaster (event.source.getEntity ()); EntityPlayerMP victim = PvPServerUtils.getMaster (event.entity); @@ -57,7 +64,12 @@ public void interceptPvP (LivingAttackEvent event) EnumPvPMode attackerMode = PvPServerUtils.getPvPMode (attacker); EnumPvPMode victimMode = PvPServerUtils.getPvPMode (victim); - boolean cancel = false; + if (MinecraftForge.EVENT_BUS + .post (new OnPvPEvent (attacker, attackerMode, victim, victimMode, event.ammount, event.source))) + { + event.setCanceled (true); + return; + } if (attackerMode != EnumPvPMode.ON) { @@ -76,11 +88,6 @@ else if (PvPServerUtils.canFly (attacker)) ServerChatUtils.red (attacker, "You have PvP disabled"); } } - cancel = true; - } - - if (cancel) - {// For performance reasons event.setCanceled (true); return; } @@ -91,56 +98,50 @@ else if (PvPServerUtils.canFly (attacker)) { ServerChatUtils.red (attacker, "This player/unit has PvP disabled"); } - cancel = true; - } - - if (cancel) - { event.setCanceled (true); + return; } - else + + if (attacker == event.source.getEntity () && victim == event.entity) { - if (attacker == event.source.getEntity () && victim == event.entity) - { - // Both involved entities are players which can attack each other + // Both involved entities are players which can attack each other - long time = PvPServerUtils.getTime (); + long time = PvPServerUtils.getTime (); - PvPData attackerData = PvPServerUtils.getPvPData (attacker); - PvPData victimData = PvPServerUtils.getPvPData (victim); + PvPData attackerData = PvPServerUtils.getPvPData (attacker); + PvPData victimData = PvPServerUtils.getPvPData (victim); - if (attackerData.getPvPTimer () == 0) - { - ServerChatUtils.yellow (attacker, "You're now in PvP combat"); - } + if (attackerData.getPvPTimer () == 0) + { + ServerChatUtils.yellow (attacker, "You're now in PvP combat"); + } - if (victimData.getPvPTimer () == 0) - { - ServerChatUtils.yellow (victim, "You're now in PvP combat"); - } + if (victimData.getPvPTimer () == 0) + { + ServerChatUtils.yellow (victim, "You're now in PvP combat"); + } - attackerData.setPvPTimer (time + config.getPvPTimer ()); - victimData.setPvPTimer (time + config.getPvPTimer ()); + attackerData.setPvPTimer (time + config.getPvPTimer ()); + victimData.setPvPTimer (time + config.getPvPTimer ()); - if (attackerData.getPvPWarmup () != 0) - { - attackerData.setPvPWarmup (0); - ServerChatUtils.yellow (attacker, "Your warmup timer was canceled because you started an attack"); - } + if (attackerData.getPvPWarmup () != 0) + { + attackerData.setPvPWarmup (0); + ServerChatUtils.yellow (attacker, "Your warmup timer was canceled because you started an attack"); + } - if (victimData.getPvPWarmup () != 0) - { - victimData.setPvPWarmup (0); - ServerChatUtils.yellow (victim, "Your warmup timer was canceled because you were attacked"); - } + if (victimData.getPvPWarmup () != 0) + { + victimData.setPvPWarmup (0); + ServerChatUtils.yellow (victim, "Your warmup timer was canceled because you were attacked"); } } } /* - * We need to log here because the LivingAttackEvent will be fired twice per - * attack. + * We need to log here because the damage in the LivingAttackEvent can change + * from the attack to the actual application of the damage. */ @SubscribeEvent public void onLivingHurt (LivingHurtEvent event)