Skip to content

Commit dad09ba

Browse files
GH-218 Add CrystalPVP expansion (#218)
* Init * Add anchor support * Add anchor to api * Add meta data contants create static methods to retrive uuid. Follow @imDMK and @coderabbitai reviews * Use coderabbit suggestion to version compatibility * Follow coderabbit review * Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/crystalpvp/CrystalPvpConstants.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Resolve review. Add documentation comment, add ignored worlds check and event.cancelled check. Fix method name to use `UniqueId` instead of `UUID` * Remove isCancelled() check -> follow coderabbit <3 * follow coderabbit - add one check for hasDamagerBlockState method declaration check * Resolve vLuckyyy review * e -> exception rename --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 690678c commit dad09ba

10 files changed

Lines changed: 385 additions & 8 deletions

File tree

eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public enum CauseOfTag {
1818
*/
1919
COMMAND,
2020

21+
/**
22+
* Crystal or anchor explosion caused the tag.
23+
*/
24+
CRYSTAL,
25+
2126
/**
2227
* A custom cause, typically defined by external plugins or systems, applied the combat tag.
2328
*/

eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.eternalcode.combat.border.animation.block.BorderBlockController;
77
import com.eternalcode.combat.border.animation.particle.ParticleController;
88
import com.eternalcode.combat.bridge.BridgeService;
9+
import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
10+
import com.eternalcode.combat.crystalpvp.EndCrystalListener;
911
import com.eternalcode.combat.fight.drop.DropKeepInventoryService;
1012
import com.eternalcode.combat.fight.FightManager;
1113
import com.eternalcode.combat.fight.drop.DropService;
@@ -175,7 +177,9 @@ public void onEnable() {
175177
new FightMessageController(this.fightManager, noticeService, pluginConfig, this.getServer()),
176178
new BorderTriggerController(borderService, () -> pluginConfig.border, fightManager, server),
177179
new ParticleController(borderService, () -> pluginConfig.border.particle, scheduler, server),
178-
new BorderBlockController(borderService, () -> pluginConfig.border.block, scheduler, server)
180+
new BorderBlockController(borderService, () -> pluginConfig.border.block, scheduler, server),
181+
new EndCrystalListener(this, this.fightManager, pluginConfig),
182+
new RespawnAnchorListener(this, this.fightManager, pluginConfig)
179183
);
180184

181185
eventManager.subscribe(

eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.eternalcode.combat.config.implementation;
22

33
import com.eternalcode.combat.border.BorderSettings;
4+
import com.eternalcode.combat.crystalpvp.CrystalPvpSettings;
45
import com.eternalcode.combat.fight.drop.DropSettings;
56
import com.eternalcode.combat.fight.effect.FightEffectSettings;
67
import com.eternalcode.combat.fight.knockback.KnockbackSettings;
@@ -70,6 +71,13 @@ public class PluginConfig extends OkaeriConfig {
7071
})
7172
public BlockPlacementSettings blockPlacement = new BlockPlacementSettings();
7273

74+
@Comment({
75+
" ",
76+
"# Settings related to crystal PvP.",
77+
"# Configure behaviors, restrictions, and features specific to crystal PvP combat."
78+
})
79+
public CrystalPvpSettings crystalPvp = new CrystalPvpSettings();
80+
7381
@Comment({
7482
" ",
7583
"# Settings related to commands during combat.",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.eternalcode.combat.crystalpvp;
2+
3+
import java.util.Optional;
4+
import java.util.UUID;
5+
import org.bukkit.metadata.MetadataValueAdapter;
6+
import org.bukkit.plugin.Plugin;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
class CrystalMetadata extends MetadataValueAdapter {
11+
12+
private UUID damager;
13+
14+
protected CrystalMetadata(@NotNull Plugin owningPlugin, UUID damager) {
15+
super(owningPlugin);
16+
this.damager = damager;
17+
}
18+
19+
Optional<UUID> getDamager() {
20+
return Optional.ofNullable(this.damager);
21+
}
22+
23+
@Override
24+
public @Nullable UUID value() {
25+
return this.damager;
26+
}
27+
28+
@Override
29+
public void invalidate() {
30+
this.damager = null;
31+
}
32+
33+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.eternalcode.combat.crystalpvp;
2+
3+
import com.eternalcode.combat.config.implementation.PluginConfig;
4+
import com.eternalcode.combat.fight.FightManager;
5+
import com.eternalcode.combat.fight.event.CauseOfTag;
6+
import com.eternalcode.combat.util.ReflectUtil;
7+
import java.util.List;
8+
import java.util.Optional;
9+
import java.util.UUID;
10+
import org.bukkit.Material;
11+
import org.bukkit.block.BlockState;
12+
import org.bukkit.entity.EnderCrystal;
13+
import org.bukkit.entity.Player;
14+
import org.bukkit.event.entity.EntityDamageByBlockEvent;
15+
import org.bukkit.event.entity.EntityDamageByEntityEvent;
16+
import org.bukkit.metadata.MetadataValue;
17+
18+
public class CrystalPvpConstants {
19+
20+
private CrystalPvpConstants() {
21+
}
22+
23+
public static final String CRYSTAL_METADATA = "eternalcombat:crystal";
24+
public static final String ANCHOR_METADATA = "eternalcombat:anchor";
25+
26+
private static final boolean HAS_DAMAGER_BLOCK_STATE = checkForDamagerBlockState();
27+
28+
private static boolean checkForDamagerBlockState() {
29+
try {
30+
return EntityDamageByBlockEvent.class.getDeclaredMethod("getDamagerBlockState") != null;
31+
}
32+
catch (NoSuchMethodException exception) {
33+
return false;
34+
}
35+
}
36+
37+
static boolean hasDamagerBlockState() {
38+
return HAS_DAMAGER_BLOCK_STATE;
39+
}
40+
41+
public static Optional<UUID> getDamagerUniqueIdFromEndCrystal(EntityDamageByEntityEvent event) {
42+
if (event.getDamager() instanceof EnderCrystal enderCrystal) {
43+
List<MetadataValue> metadataValues = enderCrystal.getMetadata(CRYSTAL_METADATA);
44+
return metadataValues
45+
.stream()
46+
.filter(source -> source instanceof CrystalMetadata)
47+
.map(meta -> (CrystalMetadata) meta)
48+
.findFirst()
49+
.flatMap(CrystalMetadata::getDamager);
50+
}
51+
return Optional.empty();
52+
}
53+
54+
public static Optional<UUID> getDamagerUniqueIdFromRespawnAnchor(EntityDamageByBlockEvent event) {
55+
if (!CrystalPvpConstants.hasDamagerBlockState()) {
56+
return Optional.empty();
57+
}
58+
59+
Object maybeState = ReflectUtil.invokeMethod(event, "getDamagerBlockState");
60+
if (!(maybeState instanceof BlockState state)) {
61+
return Optional.empty();
62+
}
63+
Material type = state.getType();
64+
if (!type.equals(Material.RESPAWN_ANCHOR)) {
65+
return Optional.empty();
66+
}
67+
68+
return state.getMetadata(ANCHOR_METADATA).stream()
69+
.filter(source -> source instanceof CrystalMetadata)
70+
.map(meta -> (CrystalMetadata) meta)
71+
.findFirst()
72+
.flatMap(metadata -> metadata.getDamager());
73+
}
74+
75+
static void handleCombatTag(
76+
Optional<UUID> optionalDamagerUUID,
77+
Player player,
78+
FightManager fightManager,
79+
PluginConfig pluginConfig
80+
) {
81+
UUID victimUniqueId = player.getUniqueId();
82+
83+
if (optionalDamagerUUID.isPresent()) {
84+
UUID damagerUniqueId = optionalDamagerUUID.get();
85+
if (!damagerUniqueId.equals(victimUniqueId)) {
86+
fightManager.tag(
87+
damagerUniqueId,
88+
pluginConfig.settings.combatTimerDuration,
89+
CauseOfTag.CRYSTAL
90+
);
91+
fightManager.tag(
92+
victimUniqueId,
93+
pluginConfig.settings.combatTimerDuration,
94+
CauseOfTag.CRYSTAL
95+
);
96+
}
97+
}
98+
}
99+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.eternalcode.combat.crystalpvp;
2+
3+
import eu.okaeri.configs.OkaeriConfig;
4+
import eu.okaeri.configs.annotation.Comment;
5+
6+
public class CrystalPvpSettings extends OkaeriConfig {
7+
8+
@Comment({"# Should player be tagged when damaged from crystal explosion set by other player"})
9+
public boolean tagFromCrystals = true;
10+
11+
12+
@Comment({"#Should player be tagged when damaged from respawn anchor explosion set by other player"})
13+
public boolean tagFromRespawnAnchor = true;
14+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.eternalcode.combat.crystalpvp;
2+
3+
import com.eternalcode.combat.config.implementation.PluginConfig;
4+
import com.eternalcode.combat.fight.FightManager;
5+
import java.util.Optional;
6+
import java.util.UUID;
7+
import org.bukkit.entity.Arrow;
8+
import org.bukkit.entity.EnderCrystal;
9+
import org.bukkit.entity.Player;
10+
import org.bukkit.event.EventHandler;
11+
import org.bukkit.event.EventPriority;
12+
import org.bukkit.event.Listener;
13+
import org.bukkit.event.entity.EntityDamageByEntityEvent;
14+
import org.bukkit.plugin.Plugin;
15+
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA;
16+
17+
public class EndCrystalListener implements Listener {
18+
19+
private final Plugin plugin;
20+
private final FightManager fightManager;
21+
private final PluginConfig pluginConfig;
22+
23+
public EndCrystalListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
24+
this.plugin = plugin;
25+
this.fightManager = fightManager;
26+
this.pluginConfig = pluginConfig;
27+
}
28+
29+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
30+
void onPlayerDamageCrystal(EntityDamageByEntityEvent event) {
31+
if (event.getEntity() instanceof EnderCrystal enderCrystal) {
32+
if (event.getDamager() instanceof Arrow arrow && arrow.getShooter() instanceof Player player) {
33+
enderCrystal.setMetadata(CRYSTAL_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId()));
34+
}
35+
36+
if (!(event.getDamager() instanceof Player player)) {
37+
return;
38+
}
39+
40+
enderCrystal.setMetadata(CRYSTAL_METADATA, new CrystalMetadata(this.plugin, player.getUniqueId()));
41+
}
42+
}
43+
44+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
45+
void onDamage(EntityDamageByEntityEvent event) {
46+
if (!this.pluginConfig.crystalPvp.tagFromCrystals) {
47+
return;
48+
}
49+
50+
if (pluginConfig.settings.ignoredWorlds.contains(event.getEntity().getWorld().getName())) {
51+
return;
52+
}
53+
54+
Optional<UUID> optionalDamagerUUID = CrystalPvpConstants.getDamagerUniqueIdFromEndCrystal(event);
55+
56+
if (optionalDamagerUUID.isEmpty()) {
57+
return;
58+
}
59+
60+
if (event.getEntity() instanceof Player player) {
61+
CrystalPvpConstants.handleCombatTag(
62+
optionalDamagerUUID,
63+
player,
64+
this.fightManager,
65+
this.pluginConfig
66+
);
67+
}
68+
}
69+
70+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.eternalcode.combat.crystalpvp;
2+
3+
import com.eternalcode.combat.config.implementation.PluginConfig;
4+
import com.eternalcode.combat.fight.FightManager;
5+
import java.util.Optional;
6+
import java.util.UUID;
7+
import org.bukkit.Material;
8+
import org.bukkit.block.Block;
9+
import org.bukkit.block.data.type.RespawnAnchor;
10+
import org.bukkit.entity.Player;
11+
import org.bukkit.event.EventHandler;
12+
import org.bukkit.event.EventPriority;
13+
import org.bukkit.event.Listener;
14+
import org.bukkit.event.block.Action;
15+
import org.bukkit.event.entity.EntityDamageByBlockEvent;
16+
import org.bukkit.event.player.PlayerInteractEvent;
17+
import org.bukkit.inventory.ItemStack;
18+
import org.bukkit.plugin.Plugin;
19+
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.ANCHOR_METADATA;
20+
21+
public class RespawnAnchorListener implements Listener {
22+
23+
private final Plugin plugin;
24+
private final FightManager fightManager;
25+
private final PluginConfig pluginConfig;
26+
27+
28+
public RespawnAnchorListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
29+
this.plugin = plugin;
30+
this.fightManager = fightManager;
31+
this.pluginConfig = pluginConfig;
32+
}
33+
34+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
35+
void onAnchorInteract(PlayerInteractEvent event) {
36+
Block block = event.getClickedBlock();
37+
if (block == null || block.getType() != Material.RESPAWN_ANCHOR) {
38+
return;
39+
}
40+
41+
if (!(block.getBlockData() instanceof RespawnAnchor respawnAnchor)) {
42+
return;
43+
}
44+
45+
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
46+
return;
47+
}
48+
49+
int charges = respawnAnchor.getCharges();
50+
51+
ItemStack item = event.getItem();
52+
boolean isGlowstone = item != null && item.getType() == Material.GLOWSTONE;
53+
54+
if ((charges > 0 && !isGlowstone) || charges == respawnAnchor.getMaximumCharges()) {
55+
addMetaData(event, block);
56+
}
57+
}
58+
59+
private void addMetaData(PlayerInteractEvent event, Block block) {
60+
block.setMetadata(
61+
ANCHOR_METADATA,
62+
new CrystalMetadata(this.plugin, event.getPlayer().getUniqueId())
63+
);
64+
}
65+
66+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
67+
void onAnchorExplosion(EntityDamageByBlockEvent event) {
68+
if (!this.pluginConfig.crystalPvp.tagFromRespawnAnchor) {
69+
return;
70+
}
71+
72+
if (pluginConfig.settings.ignoredWorlds.contains(event.getEntity().getWorld().getName())) {
73+
return;
74+
}
75+
76+
if (!(event.getEntity() instanceof Player player)) {
77+
return;
78+
}
79+
80+
Optional<UUID> optionalDamagerUniqueId = CrystalPvpConstants.getDamagerUniqueIdFromRespawnAnchor(event);
81+
82+
if (optionalDamagerUniqueId.isEmpty()) {
83+
return;
84+
}
85+
86+
CrystalPvpConstants.handleCombatTag(optionalDamagerUniqueId, player, this.fightManager, this.pluginConfig);
87+
}
88+
89+
}

0 commit comments

Comments
 (0)