4242import org .bukkit .event .entity .EntityCombustEvent ;
4343import org .bukkit .event .entity .EntityDamageByEntityEvent ;
4444import org .bukkit .event .entity .EntityDamageEvent ;
45+ import org .bukkit .event .entity .EntityKnockbackByEntityEvent ;
4546import org .bukkit .event .entity .EntityTargetEvent ;
4647import org .bukkit .event .entity .PotionSplashEvent ;
4748import org .bukkit .event .vehicle .VehicleDamageEvent ;
@@ -117,6 +118,46 @@ public void onEntityCombustByEntity(@NotNull EntityCombustByEntityEvent event) {
117118 this .handleEntityDamageEvent (new EntityDamageInstance (event ), false );
118119 }
119120
121+ // Handle wind charge knockback - wind charges deal knockback separately from damage
122+ @ EventHandler (ignoreCancelled = true , priority = EventPriority .LOWEST )
123+ public void onEntityKnockbackByEntity (@ NotNull EntityKnockbackByEntityEvent event ) {
124+ // Only handle wind charge knockback on players
125+ if (!(event .getEntity () instanceof Player defender ))
126+ return ;
127+
128+ Entity source = event .getSourceEntity ();
129+ if (source == null )
130+ return ;
131+
132+ // Check if the source is a wind charge
133+ String sourceTypeName = source .getType ().name ();
134+ if (!sourceTypeName .contains ("WIND_CHARGE" ))
135+ return ;
136+
137+ // Get the player who threw the wind charge
138+ Player attacker = null ;
139+ if (source instanceof Projectile projectile && projectile .getShooter () instanceof Player shooter ) {
140+ attacker = shooter ;
141+ }
142+
143+ // Allow self-knockback (e.g., for movement tricks)
144+ if (attacker == null || attacker == defender )
145+ return ;
146+
147+ // Only protect when PVP rules are enabled for this world
148+ if (!instance .pvpRulesApply (defender .getWorld ()))
149+ return ;
150+
151+ // Check if defender is in a PVP-protected claim
152+ PlayerData defenderData = dataStore .getPlayerData (defender .getUniqueId ());
153+ Claim claim = dataStore .getClaimAt (defender .getLocation (), false , defenderData .lastClaim );
154+ if (claim != null && instance .claimIsPvPSafeZone (claim )) {
155+ defenderData .lastClaim = claim ;
156+ event .setCancelled (true );
157+ GriefPrevention .sendRateLimitedErrorMessage (attacker , Messages .CantFightWhileImmune );
158+ }
159+ }
160+
120161 private void handleEntityDamageEvent (@ NotNull EntityDamageInstance event , boolean sendMessages ) {
121162 // monsters are never protected
122163 if (isHostile (event .damaged ()))
@@ -171,6 +212,25 @@ private void handleEntityDamageEvent(@NotNull EntityDamageInstance event, boolea
171212 }
172213 }
173214
215+ // Handle wind charge damage - wind charges are projectiles that deal knockback and damage
216+ // When PVP rules are enabled for the world, protect players in claims from wind charge attacks
217+ String damagerTypeName = damageSource != null ? damageSource .getType ().name () : "" ;
218+ if (damagerTypeName .contains ("WIND_CHARGE" ) && event .damaged () instanceof Player defender ) {
219+ if (attacker != null && attacker != defender && instance .pvpRulesApply (defender .getWorld ())) {
220+ // Check if defender is in a protected claim
221+ PlayerData defenderData = dataStore .getPlayerData (defender .getUniqueId ());
222+ Claim claim = dataStore .getClaimAt (defender .getLocation (), false , defenderData .lastClaim );
223+ if (claim != null && instance .claimIsPvPSafeZone (claim )) {
224+ defenderData .lastClaim = claim ;
225+ event .setCancelled (true );
226+ if (sendMessages ) {
227+ GriefPrevention .sendRateLimitedErrorMessage (attacker , Messages .CantFightWhileImmune );
228+ }
229+ return ;
230+ }
231+ }
232+ }
233+
174234 // Specific handling for PVP-enabled situations.
175235 if (instance .pvpRulesApply (event .damaged ().getWorld ())) {
176236 if (event .damaged () instanceof Player defender ) {
@@ -378,9 +438,8 @@ private boolean handlePvpDamageByPlayer(
378438 if (attackerData .pvpImmune || defenderData .pvpImmune ) {
379439 event .setCancelled (true );
380440 if (sendMessages )
381- GriefPrevention .sendMessage (
441+ GriefPrevention .sendRateLimitedErrorMessage (
382442 attacker ,
383- TextMode .Err ,
384443 attackerData .pvpImmune ? Messages .CantFightWhileImmune : Messages .ThatPlayerPvPImmune );
385444 return true ;
386445 }
@@ -397,7 +456,7 @@ private boolean handlePvpDamageByPlayer(
397456 Consumer <Messages > cancelHandler = message -> {
398457 event .setCancelled (true );
399458 if (sendMessages )
400- GriefPrevention .sendMessage (attacker , TextMode . Err , message );
459+ GriefPrevention .sendRateLimitedErrorMessage (attacker , message );
401460 };
402461 // Return whether PVP is handled by a claim at the attacker or defender's
403462 // locations.
@@ -472,7 +531,7 @@ private boolean handlePvpPetDamageByPlayer(
472531 if (attackerData .pvpImmune ) {
473532 event .setCancelled (true );
474533 if (sendMessages )
475- GriefPrevention .sendMessage (attacker , TextMode . Err , Messages .CantFightWhileImmune );
534+ GriefPrevention .sendRateLimitedErrorMessage (attacker , Messages .CantFightWhileImmune );
476535 return true ;
477536 }
478537
@@ -1091,7 +1150,7 @@ public void onPotionSplash(@NotNull PotionSplashEvent event) {
10911150 Consumer <Messages > cancelHandler = message -> {
10921151 event .setIntensity (affected , 0 );
10931152 if (messagedPlayer .compareAndSet (false , true ))
1094- GriefPrevention .sendMessage (thrower , TextMode . Err , message );
1153+ GriefPrevention .sendRateLimitedErrorMessage (thrower , message );
10951154 };
10961155 if (handlePvpInClaim (thrower , affectedPlayer , thrower .getLocation (), playerData ,
10971156 () -> cancelHandler .accept (Messages .CantFightWhileImmune ))) {
0 commit comments