-
-
Notifications
You must be signed in to change notification settings - Fork 8
GH-218 Add CrystalPVP expansion #218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
# Conflicts: # eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java Took 24 minutes
Took 6 minutes
WalkthroughThis update adds new features to handle combat tagging when players interact with Ender Crystals and respawn anchors. It introduces a new configuration section to toggle tagging from these interactions. Two new listener classes track damage caused by crystals and respawn anchors, tagging both the attacker and the victim during combat. Supporting classes manage metadata on crystals and anchors to remember who caused the damage. A utility class for simple method invocation via reflection is also included. The combat tag reason enum gains a new value for crystal-related tags. Finally, the plugin setup is updated to register the new listeners and use the new configuration settings. Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (13)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java (1)
74-78
: Add descriptive comments for the crystalPvp configuration section.The comment block for the new
crystalPvp
configuration is empty. All other configuration sections include helpful explanations of their purpose.Add descriptive comments similar to other sections:
@Comment({ " ", - + "# Settings related to crystal PvP and respawn anchor combat tagging.", + "# Configure whether players should be tagged when damaged by crystal or respawn anchor explosions." }) public CrystalPvpSettings crystalPvp = new CrystalPvpSettings();eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java (1)
21-24
: Enhance the JavaDoc comment for the CRYSTAL enum value.The current comment is brief compared to other enum values which provide more context.
Improve the comment to match the style of other enum values:
/** - * Crystal explosion caused the tag. + * The player was tagged in combat due to damage from an End Crystal or Respawn Anchor explosion + * caused by another player. */ CRYSTAL,eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java (2)
12-12
: Fix extra space in method signature.There's an extra space between
static
and<T>
.- public static <T> T invokeMethod(Object object, String name) { + public static <T> T invokeMethod(Object object, String name) {
11-21
: Consider providing more specific exception handling.The current implementation wraps all reflection exceptions into a generic RuntimeException, which makes it hard to diagnose issues.
Consider creating a more specific exception class or preserving the original exception message:
@SuppressWarnings("unchecked") public static <T> T invokeMethod(Object object, String name) { try { if (object == null) { return null; } Method method = object.getClass().getDeclaredMethod(name); method.setAccessible(true); return (T) method.invoke(object); } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException exception) { - throw new RuntimeException(exception); + throw new RuntimeException("Failed to invoke method " + name + " on " + object.getClass().getName(), exception); } }eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpSettings.java (2)
8-9
: Comment formatting is inconsistent with line 12There's a space after the '#' in this comment, but not in the comment on line 12.
- @Comment({"# Should player be tagged when damaged from crystal explosion set by other player"}) + @Comment({"#Should player be tagged when damaged from crystal explosion set by other player"})
12-13
: Comment wording could be improvedThe comment has a small grammar issue and could be phrased more clearly.
- @Comment({"#Should player be tagged when damaged from respawn anchor explosion set by other player"}) + @Comment({"#Should player be tagged when damaged from respawn anchor explosion set by other player"})eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalMetadata.java (1)
10-32
: Consider adding JavaDoc commentsThe class is well-structured, but adding JavaDoc comments would make it clearer to other developers.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java (2)
25-25
: Use a more descriptive metadata keyThe metadata key "eternalcombat:wao" doesn't clearly describe its purpose. Consider using something more descriptive.
- private static final String CRYSTAL_METADATA = "eternalcombat:wao"; + private static final String CRYSTAL_METADATA = "eternalcombat:crystal_damager";
57-62
: Simplify metadata extractionThe stream operations to extract metadata can be simplified for better readability.
- Optional<UUID> optionalUniqueId = damager - .stream() - .filter(source -> source instanceof CrystalMetadata) - .map(meta -> (CrystalMetadata) meta) - .findFirst() - .flatMap(metadata -> metadata.getDamager()); + Optional<UUID> optionalUniqueId = damager.stream() + .filter(CrystalMetadata.class::isInstance) + .map(CrystalMetadata.class::cast) + .findFirst() + .flatMap(CrystalMetadata::getDamager);eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (4)
29-29
: Use a more descriptive metadata keySimilar to the EndCrystalListener, the metadata key should be more descriptive.
- private static final String ANCHOR_METADATA = "eternalcombat:wao"; + private static final String ANCHOR_METADATA = "eternalcombat:anchor_damager";
90-92
: Add space consistencyThere's an extra space after
null
in the if condition.- if (state == null ) { + if (state == null) {
99-103
: Simplify metadata extractionSimilar to EndCrystalListener, this stream operation could be simplified.
- Optional<UUID> damagerOptional = state.getMetadata(ANCHOR_METADATA).stream() - .filter(source -> source instanceof CrystalMetadata) - .map(meta -> (CrystalMetadata) meta) - .findFirst() - .flatMap(metadata -> metadata.getDamager()); + Optional<UUID> damagerOptional = state.getMetadata(ANCHOR_METADATA).stream() + .filter(CrystalMetadata.class::isInstance) + .map(CrystalMetadata.class::cast) + .findFirst() + .flatMap(CrystalMetadata::getDamager);
56-82
: Consider simplifying conditions and reducing duplicationThe logic for setting metadata has repeated code blocks for different conditions.
Consider extracting the common code to a helper method:
private void setAnchorMetadata(Block block, Player player) { block.setMetadata( ANCHOR_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId()) ); }Then use this helper method in each condition.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java
(2 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java
(2 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalMetadata.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpSettings.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpSettings.java (1)
CrystalPvpSettings
(6-14)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java (2)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (1)
RespawnAnchorListener
(23-124)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java (1)
EndCrystalListener
(19-81)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java (1)
PluginConfig
(14-136)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (2)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java (1)
PluginConfig
(14-136)eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java (1)
ReflectUtil
(6-23)
🔇 Additional comments (8)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java (1)
187-188
: The new listeners look good!The EndCrystalListener and RespawnAnchorListener are properly registered with the plugin instance, fight manager, and config.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java (1)
11-21
: Document reflection usage warnings.Using reflection to access non-public methods can lead to compatibility issues with future Bukkit versions.
This utility is used to call
getDamagerBlockState
which appears to be a non-public API method. Please add a comment explaining this approach and the potential risks:@SuppressWarnings("unchecked") +/** + * Invokes a method on an object using reflection. + * Note: This bypasses access controls and may break with future API changes. + * Currently used to access getDamagerBlockState in EntityDamageByBlockEvent. + * + * @param object The object to invoke the method on + * @param name The name of the method to invoke + * @return The result of the method invocation + * @throws RuntimeException If reflection fails + */ public static <T> T invokeMethod(Object object, String name) {eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpSettings.java (1)
1-14
: Configuration class looks goodThe settings are well organized with descriptive names and good default values.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalMetadata.java (2)
10-10
: Class visibility is package-privateClass is package-private, which is good for encapsulation since it's only used within this package.
19-21
: Good use of Optional for nullable valueUsing Optional for the damager UUID is a good practice for handling possible null values safely.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java (2)
52-79
: Good condition handling for tagging playersThe code properly handles player vs. crystal damage events, checks metadata, and applies tagging to both players appropriately.
67-76
: Tag both players with the same durationBoth players are tagged with the same combat timer duration, which is good for consistency.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (1)
39-41
: Good use of early return based on configurationChecking the configuration at the start of the method and returning early is a good practice.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java
Outdated
Show resolved
Hide resolved
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java
Outdated
Show resolved
Hide resolved
Took 4 minutes
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java
Outdated
Show resolved
Hide resolved
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java
Outdated
Show resolved
Hide resolved
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java
Outdated
Show resolved
Hide resolved
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java
Outdated
Show resolved
Hide resolved
eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java
Show resolved
Hide resolved
…imDMK and @coderabbitai reviews Took 2 hours 21 minutes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java (1)
40-44
: Reflection safety reminder
Reflection may break on future Bukkit updates; wrap the call in try/catch or a version check to avoid hard errors.
🧹 Nitpick comments (5)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java (3)
35-43
: Consider simplifying optionalKiller handlingYou could build
optionalKiller
in one statement to reduce branches:Optional<UUID> optionalKiller = killer != null ? Optional.of(killer.getUniqueId()) : this.getCrystalKiller(player);It reads a little cleaner and avoids the extra
if/else
.
48-54
: Guard against attacker release when victim == attackerWhen a player kills themselves (e.g., crystal suicide),
optionalKiller
may equalplayerUniqueId
, causing a redundant seconduntag
call. A quick equality check avoids useless work.-if (optionalKiller.isPresent() && this.config.combat.releaseAttackerOnVictimDeath) { +if (optionalKiller.isPresent() + && !optionalKiller.get().equals(playerUniqueId) + && this.config.combat.releaseAttackerOnVictimDeath) {
74-84
: Null‐safe early exit
getLastDamageCause()
can returnnull
. A fast null check keeps the method tidy:EntityDamageEvent lastDamageCause = player.getLastDamageCause(); -if (lastDamageCause instanceof EntityDamageByBlockEvent ... +if (lastDamageCause == null) { + return Optional.empty(); +} +if (lastDamageCause instanceof EntityDamageByBlockEvent ...eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (1)
90-97
: Minor: streamline combat-tag callYou can inline the optional and skip an extra variable:
CrystalPvpConstants.handleCombatTag( CrystalPvpConstants.getDamagerUUIDFromRespawnAnchor(event), player, this.fightManager, this.pluginConfig);eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java (1)
57-78
: Tiny readability tweakFetching the value from the optional twice can be avoided:
optionalDamagerUUID .filter(id -> !id.equals(victimUniqueId)) .ifPresent(id -> { int duration = pluginConfig.settings.combatTimerDuration; fightManager.tag(id, duration, CauseOfTag.CRYSTAL); fightManager.tag(victimUniqueId, duration, CauseOfTag.CRYSTAL); });Same effect, a touch cleaner.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java
(2 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/ReflectUtil.java
- eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/EndCrystalListener.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java (2)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java (1)
CrystalPvpConstants
(18-81)eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java (1)
LogoutService
(11-37)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (1)
36-43
: Early config check looks good
Nice quick exit when the feature is disabled.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java
Outdated
Show resolved
Hide resolved
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java
Outdated
Show resolved
Hide resolved
Took 8 minutes
Took 2 minutes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java (1)
23-24
: 🛠️ Refactor suggestionUse unique metadata keys
Both constants are set to the same value, which risks collisions between crystal and anchor metadata. Re-using the earlier suggestion: give each a distinct suffix.
-public static final String CRYSTAL_METADATA = "eternalcombat:crystal-pvp"; -public static final String ANCHOR_METADATA = "eternalcombat:crystal-pvp"; +public static final String CRYSTAL_METADATA = "eternalcombat:crystal-pvp:crystal"; +public static final String ANCHOR_METADATA = "eternalcombat:crystal-pvp:anchor";
🧹 Nitpick comments (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java (1)
61-70
: Cache the reflective‐lookup result once
hasDamagerBlockState()
repeats reflection every call. Storing the outcome in a static final saves a tiny bit of overhead and keeps the method tidy.-private static boolean hasDamagerBlockState() { - boolean hasMethod = false; - try { - hasMethod = EntityDamageByBlockEvent.class.getDeclaredMethod("getDamagerBlockState") != null; - } - catch (NoSuchMethodException e) { - // Method does not exist - } - return hasMethod; -} +private static final boolean HAS_DAMAGER_BLOCK_STATE; +static { + boolean present; + try { + present = EntityDamageByBlockEvent.class.getDeclaredMethod("getDamagerBlockState") != null; + } catch (NoSuchMethodException ignored) { + present = false; + } + HAS_DAMAGER_BLOCK_STATE = present; +} + +static boolean hasDamagerBlockState() { + return HAS_DAMAGER_BLOCK_STATE; +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java
(1 hunks)
🔇 Additional comments (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java (1)
80-94
: Consider tagging self-damage tooIf a player hurts themselves with a crystal/anchor, they won’t be tagged because
damager == victim
. Is that intentional? Many anti-combat-log plugins still tag self-inflicted damage to prevent abuse.
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (1)
34-77
:⚠️ Potential issueMissing configuration check in onAnchorInteract method
The method doesn't check if tagging from respawn anchors is enabled before processing, unlike the onAnchorExplosion method which does this check at line 81. Add the configuration check at the beginning to maintain consistency.
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) void onAnchorInteract(PlayerInteractEvent event) { + if (!this.pluginConfig.crystalPvp.tagFromRespawnAnchor) { + return; + } Block block = event.getClickedBlock(); // rest of the method... }
🧹 Nitpick comments (3)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java (3)
41-44
: Unnecessary null check for Material typeMaterial.getType() returns an enum which can't be null. This check isn't needed and can be removed.
Material type = block.getType(); -if (type == null) { - return; -}
46-76
: Simplify code structure with early returnsThe current nested conditions make the code hard to follow. Restructure using early returns and clearer condition checks for better readability.
-if (type.equals(Material.RESPAWN_ANCHOR)) { - if (block.getBlockData() instanceof RespawnAnchor respawnAnchor) { - if (respawnAnchor.getCharges() > 0 && event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { - ItemStack item = event.getItem(); - if (item == null) { - block.setMetadata( - ANCHOR_METADATA, - new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) - ); - return; - } - - if (item.getType() != Material.GLOWSTONE) { - block.setMetadata( - ANCHOR_METADATA, - new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) - ); - return; - } - } - - if (respawnAnchor.getCharges() == respawnAnchor.getMaximumCharges() && event - .getAction() - .equals(Action.RIGHT_CLICK_BLOCK)) { - block.setMetadata( - ANCHOR_METADATA, - new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) - ); - } - } -} +if (type != Material.RESPAWN_ANCHOR) { + return; +} + +if (!(block.getBlockData() instanceof RespawnAnchor anchor)) { + return; +} + +if (!event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { + return; +} + +boolean shouldTag = false; + +ItemStack item = event.getItem(); +int charges = anchor.getCharges(); +int maxCharges = anchor.getMaximumCharges(); + +if (charges > 0 && (item == null || item.getType() != Material.GLOWSTONE)) { + shouldTag = true; +} + +if (charges == maxCharges) { + shouldTag = true; +} + +if (shouldTag) { + block.setMetadata(ANCHOR_METADATA, new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId())); +}
89-95
: Extract respawn anchor damage handling to a private methodThis code could be more readable if the damager retrieval and combat tag handling were extracted to a separate method.
-Optional<UUID> optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUUIDFromRespawnAnchor(event); - -if (optionalDamagerUniqueId.isEmpty()) { - return; -} - -CrystalPvpConstants.handleCombatTag(optionalDamagerUniqueId, player, this.fightManager, this.pluginConfig); +handleRespawnAnchorDamage(event, player);You would then add this method:
private void handleRespawnAnchorDamage(EntityDamageByBlockEvent event, Player victim) { Optional<UUID> optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUUIDFromRespawnAnchor(event); if (optionalDamagerUniqueId.isEmpty()) { return; } CrystalPvpConstants.handleCombatTag(optionalDamagerUniqueId, victim, this.fightManager, this.pluginConfig); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java
(1 hunks)eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/RespawnAnchorListener.java
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java
…talpvp/CrystalPvpConstants.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package com.eternalcode.combat.crystalpvp; | |
import com.eternalcode.combat.config.implementation.PluginConfig; | |
import com.eternalcode.combat.fight.FightManager; | |
import java.util.Optional; | |
import java.util.UUID; | |
import org.bukkit.entity.Arrow; | |
import org.bukkit.entity.EnderCrystal; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.EventPriority; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.entity.EntityDamageByEntityEvent; | |
import org.bukkit.event.entity.EntityExplodeEvent; | |
import org.bukkit.plugin.Plugin; | |
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA; | |
public class EndCrystalListener implements Listener { | |
private final Plugin plugin; | |
private final FightManager fightManager; | |
private final PluginConfig pluginConfig; | |
public EndCrystalListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) { | |
this.plugin = plugin; | |
this.fightManager = fightManager; | |
this.pluginConfig = pluginConfig; | |
} | |
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | |
void onPlayerDamageCrystal(EntityDamageByEntityEvent event) { | |
if (!(event.getEntity() instanceof EnderCrystal enderCrystal)) { | |
return; | |
} | |
if (!(event.getDamager() instanceof Player player) && | |
!(event.getDamager() instanceof Arrow arrow && arrow.getShooter() instanceof Player player)) { | |
return; | |
} | |
if (!player.hasPermission("eternalcombat.crystalpvp.crystal")) { | |
return; | |
} | |
enderCrystal.setMetadata(CRYSTAL_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId())); | |
} | |
@EventHandler(priority = EventPriority.MONITOR) | |
void onCrystalExplode(EntityExplodeEvent event) { | |
if (event.getEntity() instanceof EnderCrystal crystal) { | |
crystal.removeMetadata(CRYSTAL_METADATA, this.plugin); | |
} | |
} | |
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | |
void onDamage(EntityDamageByEntityEvent event) { | |
if (event.isCancelled()) { | |
return; | |
} | |
if (!this.pluginConfig.crystalPvp.tagFromCrystals) { | |
return; | |
} | |
if (pluginConfig.settings.ignoredWorlds.contains(event.getEntity().getWorld().getName())) { | |
return; | |
} | |
Optional<UUID> optionalDamagerUUID = CrystalPvpConstants.getDamagerUUIDFromEndCrystal(event); | |
if (optionalDamagerUUID.isEmpty()) { | |
return; | |
} | |
if (event.getEntity() instanceof Player player) { | |
CrystalPvpConstants.handleCombatTag( | |
optionalDamagerUUID, | |
player, | |
this.fightManager, | |
this.pluginConfig, | |
event.getCause() | |
); | |
} | |
} | |
} |
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | ||
void onAnchorInteract(PlayerInteractEvent event) { | ||
Block block = event.getClickedBlock(); | ||
if (block == null) { | ||
return; | ||
} | ||
|
||
Material type = block.getType(); | ||
if (type == null) { | ||
return; | ||
} | ||
|
||
if (type.equals(Material.RESPAWN_ANCHOR)) { | ||
if (block.getBlockData() instanceof RespawnAnchor respawnAnchor) { | ||
if (respawnAnchor.getCharges() > 0 && event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { | ||
ItemStack item = event.getItem(); | ||
if (item == null) { | ||
block.setMetadata( | ||
ANCHOR_METADATA, | ||
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | ||
); | ||
return; | ||
} | ||
|
||
if (item.getType() != Material.GLOWSTONE) { | ||
block.setMetadata( | ||
ANCHOR_METADATA, | ||
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | ||
); | ||
return; | ||
} | ||
} | ||
|
||
if (respawnAnchor.getCharges() == respawnAnchor.getMaximumCharges() && event | ||
.getAction() | ||
.equals(Action.RIGHT_CLICK_BLOCK)) { | ||
block.setMetadata( | ||
ANCHOR_METADATA, | ||
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | ||
); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not reca-condition?
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | |
void onAnchorInteract(PlayerInteractEvent event) { | |
Block block = event.getClickedBlock(); | |
if (block == null) { | |
return; | |
} | |
Material type = block.getType(); | |
if (type == null) { | |
return; | |
} | |
if (type.equals(Material.RESPAWN_ANCHOR)) { | |
if (block.getBlockData() instanceof RespawnAnchor respawnAnchor) { | |
if (respawnAnchor.getCharges() > 0 && event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { | |
ItemStack item = event.getItem(); | |
if (item == null) { | |
block.setMetadata( | |
ANCHOR_METADATA, | |
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | |
); | |
return; | |
} | |
if (item.getType() != Material.GLOWSTONE) { | |
block.setMetadata( | |
ANCHOR_METADATA, | |
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | |
); | |
return; | |
} | |
} | |
if (respawnAnchor.getCharges() == respawnAnchor.getMaximumCharges() && event | |
.getAction() | |
.equals(Action.RIGHT_CLICK_BLOCK)) { | |
block.setMetadata( | |
ANCHOR_METADATA, | |
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | |
); | |
} | |
} | |
} | |
} | |
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | |
void onAnchorInteract(PlayerInteractEvent event) { | |
Player player = event.getPlayer(); | |
if (!player.hasPermission("eternalcombat.crystalpvp.anchor")) { | |
return; | |
} | |
Block block = event.getClickedBlock(); | |
if (block == null) { | |
return; | |
} | |
Material type = block.getType(); | |
if (type == null || !type.equals(Material.RESPAWN_ANCHOR)) { | |
return; | |
} | |
if (block.getBlockData() instanceof RespawnAnchor respawnAnchor) { | |
synchronized (block) { | |
if (respawnAnchor.getCharges() > 0 && event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { | |
ItemStack item = event.getItem(); | |
if (item == null || item.getType() != Material.GLOWSTONE) { | |
block.setMetadata( | |
ANCHOR_METADATA, | |
new CrystalMetadata(this.plugin, player.getUniqueId()) | |
); | |
return; | |
} | |
} | |
if (respawnAnchor.getCharges() == respawnAnchor.getMaximumCharges() && | |
event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { | |
block.setMetadata( | |
ANCHOR_METADATA, | |
new CrystalMetadata(this.plugin, player.getUniqueId()) | |
); | |
} | |
} | |
} | |
} |
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleanup maybe?
@EventHandler(priority = EventPriority.MONITOR)
void onAnchorBreak(BlockBreakEvent event) {
if (event.getBlock().getType() == Material.RESPAWN_ANCHOR) {
event.getBlock().removeMetadata(ANCHOR_METADATA, this.plugin);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | ||
void onAnchorExplosion(EntityDamageByBlockEvent event) { | ||
if (!this.pluginConfig.crystalPvp.tagFromRespawnAnchor) { | ||
return; | ||
} | ||
|
||
if (!(event.getEntity() instanceof Player player)) { | ||
return; | ||
} | ||
|
||
Optional<UUID> optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUUIDFromRespawnAnchor(event); | ||
|
||
if (optionalDamagerUniqueId.isEmpty()) { | ||
return; | ||
} | ||
|
||
CrystalPvpConstants.handleCombatTag(optionalDamagerUniqueId, player, this.fightManager, this.pluginConfig); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | |
void onAnchorExplosion(EntityDamageByBlockEvent event) { | |
if (!this.pluginConfig.crystalPvp.tagFromRespawnAnchor) { | |
return; | |
} | |
if (!(event.getEntity() instanceof Player player)) { | |
return; | |
} | |
Optional<UUID> optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUUIDFromRespawnAnchor(event); | |
if (optionalDamagerUniqueId.isEmpty()) { | |
return; | |
} | |
CrystalPvpConstants.handleCombatTag(optionalDamagerUniqueId, player, this.fightManager, this.pluginConfig); | |
} | |
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | |
void onAnchorExplosion(EntityDamageByBlockEvent event) { | |
if (!this.pluginConfig.crystalPvp.tagFromRespawnAnchor) { | |
return; | |
} | |
if (!(event.getEntity() instanceof Player player)) { | |
return; | |
} | |
if (pluginConfig.settings.ignoredWorlds.contains(player.getWorld().getName())) { | |
return; | |
} | |
Optional<UUID> optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUUIDFromRespawnAnchor(event); | |
if (optionalDamagerUniqueId.isEmpty()) { | |
return; | |
} | |
CrystalPvpConstants.handleCombatTag( | |
optionalDamagerUniqueId, | |
player, | |
this.fightManager, | |
this.pluginConfig, | |
event.getCause() | |
); | |
} |
package com.eternalcode.combat.crystalpvp; | ||
|
||
import com.eternalcode.combat.config.implementation.PluginConfig; | ||
import com.eternalcode.combat.fight.FightManager; | ||
import com.eternalcode.combat.fight.event.CauseOfTag; | ||
import com.eternalcode.combat.util.ReflectUtil; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
import org.bukkit.Material; | ||
import org.bukkit.block.BlockState; | ||
import org.bukkit.entity.EnderCrystal; | ||
import org.bukkit.entity.Player; | ||
import org.bukkit.event.entity.EntityDamageByBlockEvent; | ||
import org.bukkit.event.entity.EntityDamageByEntityEvent; | ||
import org.bukkit.metadata.MetadataValue; | ||
|
||
public class CrystalPvpConstants { | ||
|
||
private CrystalPvpConstants() { | ||
} | ||
|
||
public static final String CRYSTAL_METADATA = "eternalcombat:crystal"; | ||
public static final String ANCHOR_METADATA = "eternalcombat:anchor"; | ||
|
||
public static Optional<UUID> getDamagerUUIDFromEndCrystal(EntityDamageByEntityEvent event) { | ||
if (event.getDamager() instanceof EnderCrystal enderCrystal) { | ||
List<MetadataValue> metadataValues = enderCrystal.getMetadata(CRYSTAL_METADATA); | ||
return metadataValues | ||
.stream() | ||
.filter(source -> source instanceof CrystalMetadata) | ||
.map(meta -> (CrystalMetadata) meta) | ||
.findFirst() | ||
.flatMap(CrystalMetadata::getDamager); | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
public static Optional<UUID> getDamagerUUIDFromRespawnAnchor(EntityDamageByBlockEvent event) { | ||
if (!CrystalPvpConstants.hasDamagerBlockState()) { | ||
return Optional.empty(); | ||
} | ||
|
||
Object maybeState = ReflectUtil.invokeMethod(event, "getDamagerBlockState"); | ||
if (!(maybeState instanceof BlockState state)) { | ||
return Optional.empty(); | ||
} | ||
Material type = state.getType(); | ||
if (!type.equals(Material.RESPAWN_ANCHOR)) { | ||
return Optional.empty(); | ||
} | ||
|
||
return state.getMetadata(ANCHOR_METADATA).stream() | ||
.filter(source -> source instanceof CrystalMetadata) | ||
.map(meta -> (CrystalMetadata) meta) | ||
.findFirst() | ||
.flatMap(metadata -> metadata.getDamager()); | ||
} | ||
|
||
static boolean hasDamagerBlockState() { | ||
boolean hasMethod = false; | ||
try { | ||
hasMethod = EntityDamageByBlockEvent.class.getDeclaredMethod("getDamagerBlockState") != null; | ||
} | ||
catch (NoSuchMethodException e) { | ||
// Method does not exist | ||
} | ||
return hasMethod; | ||
} | ||
|
||
static void handleCombatTag( | ||
Optional<UUID> optionalDamagerUUID, | ||
Player player, | ||
FightManager fightManager, | ||
PluginConfig pluginConfig | ||
) { | ||
UUID victimUniqueId = player.getUniqueId(); | ||
|
||
if (optionalDamagerUUID.isPresent()) { | ||
UUID damagerUniqueId = optionalDamagerUUID.get(); | ||
if (!damagerUniqueId.equals(victimUniqueId)) { | ||
fightManager.tag( | ||
damagerUniqueId, | ||
pluginConfig.settings.combatTimerDuration, | ||
CauseOfTag.CRYSTAL | ||
); | ||
fightManager.tag( | ||
victimUniqueId, | ||
pluginConfig.settings.combatTimerDuration, | ||
CauseOfTag.CRYSTAL | ||
); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package com.eternalcode.combat.crystalpvp; | |
import com.eternalcode.combat.config.implementation.PluginConfig; | |
import com.eternalcode.combat.fight.FightManager; | |
import com.eternalcode.combat.fight.event.CauseOfTag; | |
import com.eternalcode.combat.util.ReflectUtil; | |
import java.util.List; | |
import java.util.Optional; | |
import java.util.UUID; | |
import org.bukkit.Material; | |
import org.bukkit.block.BlockState; | |
import org.bukkit.entity.EnderCrystal; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.entity.EntityDamageByBlockEvent; | |
import org.bukkit.event.entity.EntityDamageByEntityEvent; | |
import org.bukkit.metadata.MetadataValue; | |
public class CrystalPvpConstants { | |
private CrystalPvpConstants() { | |
} | |
public static final String CRYSTAL_METADATA = "eternalcombat:crystal"; | |
public static final String ANCHOR_METADATA = "eternalcombat:anchor"; | |
public static Optional<UUID> getDamagerUUIDFromEndCrystal(EntityDamageByEntityEvent event) { | |
if (event.getDamager() instanceof EnderCrystal enderCrystal) { | |
List<MetadataValue> metadataValues = enderCrystal.getMetadata(CRYSTAL_METADATA); | |
return metadataValues | |
.stream() | |
.filter(source -> source instanceof CrystalMetadata) | |
.map(meta -> (CrystalMetadata) meta) | |
.findFirst() | |
.flatMap(CrystalMetadata::getDamager); | |
} | |
return Optional.empty(); | |
} | |
public static Optional<UUID> getDamagerUUIDFromRespawnAnchor(EntityDamageByBlockEvent event) { | |
if (!CrystalPvpConstants.hasDamagerBlockState()) { | |
return Optional.empty(); | |
} | |
Object maybeState = ReflectUtil.invokeMethod(event, "getDamagerBlockState"); | |
if (!(maybeState instanceof BlockState state)) { | |
return Optional.empty(); | |
} | |
Material type = state.getType(); | |
if (!type.equals(Material.RESPAWN_ANCHOR)) { | |
return Optional.empty(); | |
} | |
return state.getMetadata(ANCHOR_METADATA).stream() | |
.filter(source -> source instanceof CrystalMetadata) | |
.map(meta -> (CrystalMetadata) meta) | |
.findFirst() | |
.flatMap(metadata -> metadata.getDamager()); | |
} | |
static boolean hasDamagerBlockState() { | |
boolean hasMethod = false; | |
try { | |
hasMethod = EntityDamageByBlockEvent.class.getDeclaredMethod("getDamagerBlockState") != null; | |
} | |
catch (NoSuchMethodException e) { | |
// Method does not exist | |
} | |
return hasMethod; | |
} | |
static void handleCombatTag( | |
Optional<UUID> optionalDamagerUUID, | |
Player player, | |
FightManager fightManager, | |
PluginConfig pluginConfig | |
) { | |
UUID victimUniqueId = player.getUniqueId(); | |
if (optionalDamagerUUID.isPresent()) { | |
UUID damagerUniqueId = optionalDamagerUUID.get(); | |
if (!damagerUniqueId.equals(victimUniqueId)) { | |
fightManager.tag( | |
damagerUniqueId, | |
pluginConfig.settings.combatTimerDuration, | |
CauseOfTag.CRYSTAL | |
); | |
fightManager.tag( | |
victimUniqueId, | |
pluginConfig.settings.combatTimerDuration, | |
CauseOfTag.CRYSTAL | |
); | |
} | |
} | |
} | |
} | |
package com.eternalcode.combat.crystalpvp; | |
import com.eternalcode.combat.config.implementation.PluginConfig; | |
import com.eternalcode.combat.fight.FightManager; | |
import com.eternalcode.combat.fight.event.CauseOfTag; | |
import com.eternalcode.combat.util.ReflectUtil; | |
import java.lang.reflect.Method; | |
import java.util.List; | |
import java.util.Optional; | |
import java.util.UUID; | |
import org.bukkit.Material; | |
import org.bukkit.block.BlockState; | |
import org.bukkit.entity.EnderCrystal; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.entity.EntityDamageByBlockEvent; | |
import org.bukkit.event.entity.EntityDamageByEntityEvent; | |
import org.bukkit.event.entity.EntityDamageEvent; | |
import org.bukkit.metadata.MetadataValue; | |
public class CrystalPvpConstants { | |
private CrystalPvpConstants() { | |
} | |
public static final String CRYSTAL_METADATA = "eternalcombat:crystal"; | |
public static final String ANCHOR_METADATA = "eternalcombat:anchor"; | |
public static Optional<UUID> getDamagerUUIDFromEndCrystal(EntityDamageByEntityEvent event) { | |
if (event.getDamager() instanceof EnderCrystal enderCrystal) { | |
List<MetadataValue> metadataValues = enderCrystal.getMetadata(CRYSTAL_METADATA); | |
if (metadataValues == null || metadataValues.isEmpty()) { | |
return Optional.empty(); | |
} | |
return metadataValues | |
.stream() | |
.filter(source -> source instanceof CrystalMetadata) | |
.map(meta -> (CrystalMetadata) meta) | |
.findFirst() | |
.flatMap(CrystalMetadata::getDamager); | |
} | |
return Optional.empty(); | |
} | |
public static Optional<UUID> getDamagerUUIDFromRespawnAnchor(EntityDamageByBlockEvent event) { | |
if (!CrystalPvpConstants.hasDamagerBlockState()) { | |
return Optional.empty(); | |
} | |
Object maybeState = ReflectUtil.invokeMethod(event, "getDamagerBlockState"); | |
if (!(maybeState instanceof BlockState state)) { | |
return Optional.empty(); | |
} | |
Material type = state.getType(); | |
if (!type.equals(Material.RESPAWN_ANCHOR)) { | |
return Optional.empty(); | |
} | |
List<MetadataValue> metadataValues = state.getMetadata(ANCHOR_METADATA); | |
if (metadataValues == null || metadataValues.isEmpty()) { | |
return Optional.empty(); | |
} | |
return metadataValues.stream() | |
.filter(source -> source instanceof CrystalMetadata) | |
.map(meta -> (CrystalMetadata) meta) | |
.findFirst() | |
.flatMap(metadata -> metadata.getDamager()); | |
} | |
static boolean hasDamagerBlockState() { | |
try { | |
Method method = EntityDamageByBlockEvent.class.getDeclaredMethod("getDamagerBlockState"); | |
return method != null && method.getReturnType().equals(BlockState.class); | |
} catch (NoSuchMethodException e) { | |
return false; | |
} | |
} | |
static void handleCombatTag( | |
Optional<UUID> optionalDamagerUUID, | |
Player player, | |
FightManager fightManager, | |
PluginConfig pluginConfig, | |
EntityDamageEvent.DamageCause cause | |
) { | |
if (cause != EntityDamageEvent.DamageCause.BLOCK_EXPLOSION && | |
cause != EntityDamageEvent.DamageCause.ENTITY_EXPLOSION) { | |
return; | |
} | |
UUID victimUniqueId = player.getUniqueId(); | |
if (optionalDamagerUUID.isPresent()) { | |
UUID damagerUniqueId = optionalDamagerUUID.get(); | |
if (!damagerUniqueId.equals(victimUniqueId)) { | |
fightManager.tag( | |
damagerUniqueId, | |
pluginConfig.settings.combatTimerDuration, | |
CauseOfTag.CRYSTAL | |
); | |
fightManager.tag( | |
victimUniqueId, | |
pluginConfig.settings.combatTimerDuration, | |
CauseOfTag.CRYSTAL | |
); | |
} | |
} | |
} | |
} |
@Comment({ | ||
" ", | ||
|
||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm? why empty line?
import org.bukkit.event.entity.EntityDamageByEntityEvent; | ||
import org.bukkit.metadata.MetadataValue; | ||
|
||
public class CrystalPvpConstants { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if this class is named right @Rollczi?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Pvp
has only the first letter uppercased, then the method below should be renamed to getDamagerUuid
FromEndCrystal
block.setMetadata( | ||
ANCHOR_METADATA, | ||
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | ||
); | ||
return; | ||
} | ||
|
||
if (item.getType() != Material.GLOWSTONE) { | ||
block.setMetadata( | ||
ANCHOR_METADATA, | ||
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) | ||
); | ||
return; | ||
} | ||
} | ||
|
||
if (respawnAnchor.getCharges() == respawnAnchor.getMaximumCharges() && event | ||
.getAction() | ||
.equals(Action.RIGHT_CLICK_BLOCK)) { | ||
block.setMetadata( | ||
ANCHOR_METADATA, | ||
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I see we can extract the block.setMetadata(...)
at the beginning/end of the method, it's repetitive
Add support for crystal pvp and anchor pvp.