869 lines
46 KiB
Java
869 lines
46 KiB
Java
package cn.infstar.magicgem.skill;
|
||
|
||
import cn.infstar.magicgem.api.event.MagicEquipmentChangeEvent;
|
||
import cn.infstar.magicgem.api.event.MagicJumpEvent;
|
||
import cn.infstar.magicgem.api.event.MagicPotionBuffEvent;
|
||
import cn.infstar.magicgem.api.event.MagicSkillUseEvent;
|
||
import cn.infstar.magicgem.condition.ConditionEngine;
|
||
import cn.infstar.magicgem.dsl.Directive;
|
||
import cn.infstar.magicgem.dsl.DirectiveParser;
|
||
import cn.infstar.magicgem.expression.DynamicExpression;
|
||
import cn.infstar.magicgem.item.ItemCodec;
|
||
import cn.infstar.magicgem.integration.MythicBridge;
|
||
import cn.infstar.magicgem.integration.ProtectionBridge;
|
||
import cn.infstar.magicgem.reward.RewardContext;
|
||
import cn.infstar.magicgem.reward.RewardEngine;
|
||
import cn.infstar.magicgem.reward.RewardException;
|
||
import cn.infstar.magicgem.util.MessageDispatcher;
|
||
import cn.infstar.magicgem.util.PotionTypes;
|
||
import de.tr7zw.changeme.nbtapi.NBT;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.ChatColor;
|
||
import org.bukkit.Location;
|
||
import org.bukkit.Material;
|
||
import org.bukkit.NamespacedKey;
|
||
import org.bukkit.Registry;
|
||
import org.bukkit.entity.Entity;
|
||
import org.bukkit.entity.LivingEntity;
|
||
import org.bukkit.entity.Player;
|
||
import org.bukkit.entity.Projectile;
|
||
import org.bukkit.event.Cancellable;
|
||
import org.bukkit.event.Event;
|
||
import org.bukkit.event.EventHandler;
|
||
import org.bukkit.event.EventPriority;
|
||
import org.bukkit.event.Listener;
|
||
import org.bukkit.event.block.Action;
|
||
import org.bukkit.event.block.BlockBreakEvent;
|
||
import org.bukkit.event.block.BlockPlaceEvent;
|
||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||
import org.bukkit.event.entity.EntityDamageEvent;
|
||
import org.bukkit.event.entity.EntityDeathEvent;
|
||
import org.bukkit.event.entity.EntityRegainHealthEvent;
|
||
import org.bukkit.event.entity.EntityShootBowEvent;
|
||
import org.bukkit.event.entity.EntityTameEvent;
|
||
import org.bukkit.event.entity.EntityTeleportEvent;
|
||
import org.bukkit.event.entity.LingeringPotionSplashEvent;
|
||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||
import org.bukkit.event.entity.PotionSplashEvent;
|
||
import org.bukkit.event.entity.ProjectileHitEvent;
|
||
import org.bukkit.event.entity.ProjectileLaunchEvent;
|
||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||
import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent;
|
||
import com.destroystokyo.paper.event.player.PlayerJumpEvent;
|
||
import org.bukkit.event.player.PlayerAnimationEvent;
|
||
import org.bukkit.event.player.PlayerAnimationType;
|
||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||
import org.bukkit.event.player.PlayerFishEvent;
|
||
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
|
||
import org.bukkit.event.player.PlayerInteractEvent;
|
||
import org.bukkit.event.player.PlayerItemConsumeEvent;
|
||
import org.bukkit.event.player.PlayerItemBreakEvent;
|
||
import org.bukkit.event.player.PlayerItemHeldEvent;
|
||
import org.bukkit.event.player.PlayerJoinEvent;
|
||
import org.bukkit.event.player.PlayerMoveEvent;
|
||
import org.bukkit.event.player.PlayerQuitEvent;
|
||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||
import org.bukkit.event.player.PlayerToggleFlightEvent;
|
||
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
|
||
import org.bukkit.inventory.EquipmentSlot;
|
||
import org.bukkit.inventory.ItemStack;
|
||
import org.bukkit.persistence.PersistentDataType;
|
||
import org.bukkit.plugin.java.JavaPlugin;
|
||
import org.bukkit.potion.PotionEffect;
|
||
import org.bukkit.potion.PotionEffectType;
|
||
import org.bukkit.projectiles.ProjectileSource;
|
||
import org.bukkit.util.Vector;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.Collection;
|
||
import java.util.EnumMap;
|
||
import java.util.HashMap;
|
||
import java.util.LinkedHashSet;
|
||
import java.util.List;
|
||
import java.util.Locale;
|
||
import java.util.Map;
|
||
import java.util.Set;
|
||
import java.util.UUID;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import java.util.concurrent.ThreadLocalRandom;
|
||
|
||
public final class SkillEngine implements Listener {
|
||
private final JavaPlugin plugin;
|
||
private final SkillRegistry registry;
|
||
private final ItemCodec itemCodec;
|
||
private final DirectiveParser parser;
|
||
private final DynamicExpression expressions;
|
||
private final ConditionEngine conditions;
|
||
private final RewardEngine rewards;
|
||
private final MythicBridge mythic;
|
||
private final ProtectionBridge protection;
|
||
private final MessageDispatcher messages;
|
||
private final Map<UUID, Map<String, Long>> cooldowns = new ConcurrentHashMap<>();
|
||
private final Map<UUID, Map<String, Long>> durations = new ConcurrentHashMap<>();
|
||
private final Map<String, Long> debuffs = new ConcurrentHashMap<>();
|
||
private final Map<String, CounterState> counters = new ConcurrentHashMap<>();
|
||
private long ticks;
|
||
|
||
public SkillEngine(JavaPlugin plugin, SkillRegistry registry, ItemCodec itemCodec, DirectiveParser parser,
|
||
DynamicExpression expressions, ConditionEngine conditions, RewardEngine rewards,
|
||
MythicBridge mythic, ProtectionBridge protection, MessageDispatcher messages) {
|
||
this.plugin = plugin;
|
||
this.registry = registry;
|
||
this.itemCodec = itemCodec;
|
||
this.parser = parser;
|
||
this.expressions = expressions;
|
||
this.conditions = conditions;
|
||
this.rewards = rewards;
|
||
this.mythic = mythic;
|
||
this.protection = protection;
|
||
this.messages = messages;
|
||
Bukkit.getScheduler().runTaskTimer(plugin, this::timer, 1, 1);
|
||
}
|
||
|
||
public void invoke(Player player, String skillName) {
|
||
SkillDefinition skill = registry.find(skillName).orElse(null);
|
||
if (skill == null) return;
|
||
ItemStack item = player.getInventory().getItemInMainHand();
|
||
execute(player, item, "mainhand", skill, TriggerType.DEFAULT, List.of(), null,
|
||
new EventContext(null, player.getLocation()));
|
||
}
|
||
|
||
public void signal(Player player, String signal) {
|
||
trigger(player, TriggerType.SIGNAL, List.of(signal), null, null, null);
|
||
}
|
||
|
||
public void mythicKill(Player player, String mobId, Entity mob, Event event) {
|
||
trigger(player, TriggerType.KILL_MYTHICMOBS, List.of(mobId), mob, mob.getLocation(), event);
|
||
}
|
||
|
||
private void timer() {
|
||
ticks++;
|
||
if (ticks % 1200 == 0) {
|
||
long now = System.currentTimeMillis();
|
||
debuffs.entrySet().removeIf(entry -> entry.getValue() <= now);
|
||
counters.entrySet().removeIf(entry -> now - entry.getValue().lastUsed() > 1_800_000L);
|
||
}
|
||
for (Player player : Bukkit.getOnlinePlayers())
|
||
trigger(player, TriggerType.TIMER, List.of(Long.toString(ticks)), null, null, null);
|
||
}
|
||
|
||
private void trigger(Player player, TriggerType trigger, List<String> information,
|
||
Entity target, Location targetLocation, Event event) {
|
||
if (debuffed(player, "SKILL")) return;
|
||
for (EquippedItem equipped : equipment(player)) {
|
||
for (SkillDefinition skill : skills(equipped.item(), equipped.slot()))
|
||
execute(player, equipped.item(), equipped.slot(), skill, trigger, information, target,
|
||
new EventContext(event, targetLocation));
|
||
}
|
||
}
|
||
|
||
private void triggerItem(Player player, ItemStack item, String slot, TriggerType trigger,
|
||
List<String> information, Entity target, Location targetLocation, Event event) {
|
||
if (debuffed(player, "SKILL")) return;
|
||
for (SkillDefinition skill : skills(item, slot))
|
||
execute(player, item, slot, skill, trigger, information, target, new EventContext(event, targetLocation));
|
||
}
|
||
|
||
private void execute(Player player, ItemStack item, String slot, SkillDefinition skill, TriggerType trigger,
|
||
List<String> information, Entity target, EventContext eventContext) {
|
||
List<SkillAction> actions = skill.actions().stream()
|
||
.filter(action -> action.trigger() == trigger)
|
||
.filter(action -> filter(action, information, trigger))
|
||
.toList();
|
||
if (actions.isEmpty()) return;
|
||
if (!checkConditions(skill.conditions(), player, player, item)) return;
|
||
if (!checkConditions(skill.triggerConditions(), player, player, item)) return;
|
||
if (!checkConditions(skill.targetConditions(), player, target, item)) return;
|
||
long now = System.currentTimeMillis();
|
||
String stateKey = skill.name().toLowerCase(Locale.ROOT);
|
||
boolean timed = skill.duration() > 0;
|
||
boolean active = durations.computeIfAbsent(player.getUniqueId(), ignored -> new ConcurrentHashMap<>())
|
||
.getOrDefault(stateKey, 0L) > now;
|
||
long ready = cooldowns.computeIfAbsent(player.getUniqueId(), ignored -> new ConcurrentHashMap<>())
|
||
.getOrDefault(stateKey, 0L);
|
||
if (!active && ready > now) {
|
||
messages.cooldown(player, skill.cooldownTip(), (ready - now) / 1000.0, skill.cooldown());
|
||
return;
|
||
}
|
||
if (!roll(skill.success(), player, item)) {
|
||
tip(player, skill.failTip());
|
||
return;
|
||
}
|
||
MagicSkillUseEvent useEvent = new MagicSkillUseEvent(player, skill, item);
|
||
Bukkit.getPluginManager().callEvent(useEvent);
|
||
if (useEvent.isCancelled()) return;
|
||
boolean executed = false;
|
||
for (SkillAction action : actions) {
|
||
String type = normalize(action.mechanic().type());
|
||
if (timed && !active && !type.equals("timestart")) continue;
|
||
if (timed && active && type.equals("timestart")) continue;
|
||
try {
|
||
mechanic(player, item, slot, skill, action, target, eventContext);
|
||
executed = true;
|
||
} catch (RewardException | RuntimeException exception) {
|
||
plugin.getLogger().warning("技能 " + skill.name() + " 执行失败: " + exception.getMessage());
|
||
}
|
||
}
|
||
if (executed && !active && skill.cooldown() > 0)
|
||
cooldowns.get(player.getUniqueId()).put(stateKey, now + (long) (skill.cooldown() * 1000));
|
||
if (executed) tip(player, skill.successTip());
|
||
}
|
||
|
||
private void mechanic(Player player, ItemStack item, String slot, SkillDefinition skill, SkillAction action,
|
||
Entity target, EventContext context) throws RewardException {
|
||
Directive mechanic = action.mechanic();
|
||
switch (normalize(mechanic.type())) {
|
||
case "reward" -> {
|
||
ItemStack changed = applyReward(player, item, mechanic, false);
|
||
if (changed != item) replaceMatchingItem(player, item, changed);
|
||
}
|
||
case "rewardswitch" -> { }
|
||
case "switch" -> switchSkill(player, item, skill, mechanic);
|
||
case "potionbuff" -> potionBuff(player, item, mechanic, action.target(), target, action.worldLimits());
|
||
case "blink" -> blink(player, item, mechanic, action.target(), target, context.location());
|
||
case "timestart" -> timeStart(player, item, skill, mechanic);
|
||
case "timeadd" -> timeAdd(player, item, skill, mechanic);
|
||
case "debuff" -> debuff(player, item, mechanic, action.target(), target);
|
||
case "bloodsuck", "bloodsuck", "blooksuck" -> bloodSuck(player, item, mechanic, context.event());
|
||
case "shielddamage" -> shieldDamage(player, item, mechanic, action.target(), target);
|
||
case "damage" -> damage(player, item, mechanic, action.target(), target);
|
||
case "counter" -> counter(player, item, slot, mechanic, target, context);
|
||
case "cancelevent" -> { if (context.event() instanceof Cancellable cancellable) cancellable.setCancelled(true); }
|
||
default -> {
|
||
Location location = context.location() != null ? context.location()
|
||
: target == null ? player.getLocation() : target.getLocation();
|
||
if (!mythic.castInline(player, action.mechanicLine(), target, location)
|
||
&& !mythic.cast(player, mechanic.type(), target, location))
|
||
plugin.getLogger().warning("未知技能机制或 MythicMobs 技能: " + mechanic.type());
|
||
}
|
||
}
|
||
}
|
||
|
||
private ItemStack applyReward(Player player, ItemStack item, Directive mechanic, boolean removal)
|
||
throws RewardException {
|
||
String nestedSource = mechanic.positional().isEmpty() ? mechanic.argument("", "reward", "r")
|
||
: mechanic.positional().getFirst();
|
||
Directive nested = parser.parse(nestedSource);
|
||
RewardContext rewardContext = new RewardContext(player, null, item, removal);
|
||
rewards.applyDirective(rewardContext, nested);
|
||
return rewardContext.item();
|
||
}
|
||
|
||
private void switchSkill(Player player, ItemStack item, SkillDefinition source, Directive mechanic)
|
||
throws RewardException {
|
||
List<String> cycle = new ArrayList<>();
|
||
String first = mechanic.argument("", "skills", "list", "s");
|
||
if (!first.isBlank()) cycle.addAll(parser.splitTopLevel(first, ';'));
|
||
cycle.addAll(mechanic.positional());
|
||
if (cycle.isEmpty()) return;
|
||
List<String> current = new ArrayList<>(itemCodec.skills(item));
|
||
int selected = -1;
|
||
for (int index = 0; index < cycle.size(); index++) if (current.contains(cycle.get(index))) { selected = index; break; }
|
||
String previous = selected < 0 ? null : cycle.get(selected);
|
||
String next = cycle.get((selected + 1) % cycle.size());
|
||
ItemStack changed = item;
|
||
if (previous != null) {
|
||
SkillDefinition oldSkill = registry.find(previous).orElse(null);
|
||
if (oldSkill != null) for (SkillAction action : oldSkill.actions())
|
||
if (normalize(action.mechanic().type()).equals("rewardswitch"))
|
||
changed = applyReward(player, changed, action.mechanic(), true);
|
||
current.remove(previous);
|
||
}
|
||
if (!current.contains(next)) current.add(next);
|
||
changed = itemCodec.setSkills(changed, current);
|
||
SkillDefinition newSkill = registry.find(next).orElse(null);
|
||
if (newSkill != null) for (SkillAction action : newSkill.actions())
|
||
if (normalize(action.mechanic().type()).equals("rewardswitch"))
|
||
changed = applyReward(player, changed, action.mechanic(), false);
|
||
replaceMatchingItem(player, item, changed);
|
||
}
|
||
|
||
private void potionBuff(Player player, ItemStack item, Directive mechanic, String selector, Entity target,
|
||
Map<String, Integer> worldLimits)
|
||
throws RewardException {
|
||
LivingEntity living = selectedLiving(player, selector, target);
|
||
if (living == null) return;
|
||
if (debuffed(living, "POTIONBUFF")) return;
|
||
String name = mechanic.argument("", "type", "potion", "p", "name").toLowerCase(Locale.ROOT);
|
||
PotionEffectType type = PotionTypes.resolve(name);
|
||
if (type == null) throw new RewardException("未知药水效果: " + name);
|
||
double levelValue = mechanic.booleanArgument(false, "roman")
|
||
? expressions.evaluateRoman(mechanic.argument("1", "level", "l"), player, item, Map.of())
|
||
: value(mechanic.argument("1", "level", "l"), player, item);
|
||
int level = Math.max(1, (int) Math.round(levelValue));
|
||
String world = living.getWorld().getName().toLowerCase(Locale.ROOT);
|
||
Integer actionLimit = worldLimits.get(world);
|
||
String globalPath = "potion-global-limit." + living.getWorld().getName();
|
||
Integer globalLimit = plugin.getConfig().contains(globalPath) ? plugin.getConfig().getInt(globalPath) : null;
|
||
if (actionLimit != null) level = Math.min(level, Math.max(0, actionLimit));
|
||
if (globalLimit != null) level = Math.min(level, Math.max(0, globalLimit));
|
||
if (level <= 0) return;
|
||
int duration = Math.max(1, (int) Math.round(value(mechanic.argument("1", "duration", "d"), player, item) * 20));
|
||
int refresh = Math.max(0, (int) Math.round(value(mechanic.argument("0", "refresh"), player, item) * 20));
|
||
PotionEffect old = living.getPotionEffect(type);
|
||
if (old != null && (old.getAmplifier() > level - 1 || old.getDuration() > refresh)) return;
|
||
MagicPotionBuffEvent event = new MagicPotionBuffEvent(player, living, type, level - 1, duration);
|
||
Bukkit.getPluginManager().callEvent(event);
|
||
if (!event.isCancelled()) living.addPotionEffect(new PotionEffect(type, event.getDurationTicks(),
|
||
event.getAmplifier(), true, true, true), true);
|
||
}
|
||
|
||
private void blink(Player player, ItemStack item, Directive mechanic, String selector, Entity target,
|
||
Location eventLocation) {
|
||
double distance = Math.max(0, value(mechanic.argument("5", "distance", "d"), player, item));
|
||
Location destination = switch (selector.toLowerCase(Locale.ROOT)) {
|
||
case "entity", "target", "targetentity" -> target == null ? null : target.getLocation();
|
||
case "location" -> eventLocation;
|
||
case "bed" -> player.getBedSpawnLocation();
|
||
case "up" -> player.getLocation().add(0, distance, 0);
|
||
case "down" -> player.getLocation().add(0, -distance, 0);
|
||
case "left" -> directional(player, distance, -90);
|
||
case "right" -> directional(player, distance, 90);
|
||
case "backward", "back" -> directional(player, distance, 180);
|
||
default -> directional(player, distance, 0);
|
||
};
|
||
if (destination == null) return;
|
||
Location safe = safeDestination(player.getLocation(), destination, distance);
|
||
if (safe != null) player.teleport(safe);
|
||
}
|
||
|
||
private Location directional(Player player, double distance, double yawOffset) {
|
||
Location origin = player.getLocation();
|
||
Location facing = origin.clone();
|
||
facing.setYaw(facing.getYaw() + (float) yawOffset);
|
||
Vector direction = facing.getDirection().normalize();
|
||
return origin.clone().add(direction.multiply(distance));
|
||
}
|
||
|
||
private Location safeDestination(Location origin, Location requested, double limit) {
|
||
Vector delta = requested.toVector().subtract(origin.toVector());
|
||
if (limit > 0 && delta.length() > limit) requested = origin.clone().add(delta.normalize().multiply(limit));
|
||
for (int step = 0; step < 4; step++) {
|
||
Location candidate = requested.clone().add(0, step, 0);
|
||
if (candidate.getBlock().isPassable() && candidate.clone().add(0, 1, 0).getBlock().isPassable()) return candidate;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private void timeStart(Player player, ItemStack item, SkillDefinition skill, Directive mechanic) {
|
||
durations.computeIfAbsent(player.getUniqueId(), ignored -> new ConcurrentHashMap<>())
|
||
.put(skill.name().toLowerCase(Locale.ROOT), System.currentTimeMillis() + (long) (skill.duration() * 1000));
|
||
tip(player, mechanic.argument("", "tip"));
|
||
}
|
||
|
||
private void timeAdd(Player player, ItemStack item, SkillDefinition skill, Directive mechanic) {
|
||
long addition = (long) (value(mechanic.argument("0", "time", "duration"), player, item) * 1000);
|
||
durations.computeIfAbsent(player.getUniqueId(), ignored -> new ConcurrentHashMap<>())
|
||
.merge(skill.name().toLowerCase(Locale.ROOT), addition, Long::sum);
|
||
tip(player, mechanic.argument("", "tip"));
|
||
}
|
||
|
||
private void debuff(Player player, ItemStack item, Directive mechanic, String selector, Entity target) {
|
||
Entity selected = selector.equalsIgnoreCase("self") ? player : target;
|
||
if (selected == null) return;
|
||
String type = mechanic.argument("SKILL", "type").toUpperCase(Locale.ROOT);
|
||
long end = System.currentTimeMillis() + (long) (value(mechanic.argument("1", "duration", "d"), player, item) * 1000);
|
||
debuffs.put(selected.getUniqueId() + ":" + type, end);
|
||
}
|
||
|
||
private void bloodSuck(Player player, ItemStack item, Directive mechanic, Event event) {
|
||
if (!(event instanceof EntityDamageEvent damage)) return;
|
||
double factor = value(mechanic.argument("0", "factor", "f", "k"), player, item);
|
||
player.setHealth(Math.min(player.getMaxHealth(), player.getHealth() + Math.max(0, damage.getFinalDamage() * factor)));
|
||
}
|
||
|
||
private void shieldDamage(Player player, ItemStack item, Directive mechanic, String selector, Entity target) {
|
||
LivingEntity living = selectedLiving(player, selector, target);
|
||
if (living == null) return;
|
||
double damage = Math.max(0, value(mechanic.argument("0", "damage", "amount", "d"), player, item));
|
||
living.setAbsorptionAmount(Math.max(0, living.getAbsorptionAmount() - damage));
|
||
}
|
||
|
||
private void damage(Player player, ItemStack item, Directive mechanic, String selector, Entity target) {
|
||
LivingEntity living = selectedLiving(player, selector, target);
|
||
if (living == null) return;
|
||
living.damage(Math.max(0, value(mechanic.argument("0", "damage", "amount", "d"), player, item)), player);
|
||
}
|
||
|
||
private void counter(Player player, ItemStack item, String slot, Directive mechanic, Entity target,
|
||
EventContext context) {
|
||
String tag = mechanic.argument("default", "tag");
|
||
long count;
|
||
if (mechanic.booleanArgument(false, "persist", "p")) {
|
||
String key = "magicgem_reforged_counter_" + Integer.toHexString(tag.hashCode());
|
||
count = NBT.get(item, nbt -> { return nbt.hasTag(key) ? nbt.getLong(key) + 1 : 1L; });
|
||
long stored = count;
|
||
NBT.modify(item, nbt -> { nbt.setLong(key, stored); });
|
||
} else {
|
||
String itemId = NBT.get(item, nbt -> nbt.hasTag("MAGICGEM_REFORGED_RUNTIME_ID")
|
||
? nbt.getString("MAGICGEM_REFORGED_RUNTIME_ID") : "");
|
||
if (itemId.isBlank()) {
|
||
itemId = UUID.randomUUID().toString();
|
||
String generated = itemId;
|
||
NBT.modify(item, nbt -> { nbt.setString("MAGICGEM_REFORGED_RUNTIME_ID", generated); });
|
||
}
|
||
String key = player.getUniqueId() + ":" + itemId + ':' + slot + ':'
|
||
+ Integer.toUnsignedString(mechanic.source().hashCode(), 36) + ':' + tag;
|
||
long now = System.currentTimeMillis();
|
||
CounterState state = counters.compute(key, (ignored, current) ->
|
||
new CounterState(current == null ? 1 : current.count() + 1, now));
|
||
count = state.count();
|
||
}
|
||
double dynamic = value(mechanic.argument("0", "dynamic", "d"), player, item);
|
||
if (!expressions.test(mechanic.argument("true", "condition", "c"), player, item,
|
||
Map.of("v", (double) count, "d", dynamic))) return;
|
||
List<String> information = List.of(tag);
|
||
if (mechanic.booleanArgument(false, "allslot"))
|
||
trigger(player, TriggerType.COUNTER, information, target, context.location(), context.event());
|
||
else triggerItem(player, item, slot, TriggerType.COUNTER, information, target, context.location(), context.event());
|
||
}
|
||
|
||
private boolean filter(SkillAction action, List<String> information, TriggerType trigger) {
|
||
if (action.triggerFilters().isEmpty()) return true;
|
||
if (trigger == TriggerType.TIMER) {
|
||
try {
|
||
long interval = Math.max(1, Long.parseLong(action.triggerFilters().getFirst()));
|
||
return ticks % interval == 0;
|
||
} catch (NumberFormatException ignored) { return false; }
|
||
}
|
||
for (String expected : action.triggerFilters())
|
||
for (String actual : information)
|
||
if (actual.equalsIgnoreCase(expected)) return true;
|
||
return false;
|
||
}
|
||
|
||
private boolean checkConditions(List<Directive> checks, Player caster, Entity subject, ItemStack item) {
|
||
for (Directive check : checks) {
|
||
String type = normalize(check.type());
|
||
boolean passed = switch (type) {
|
||
case "noarmorstand" -> subject == null || subject.getType() != org.bukkit.entity.EntityType.ARMOR_STAND;
|
||
case "pvpmanager" -> subject instanceof Player player && protection.pvpEnabled(player);
|
||
case "residenceflag", "redidenseflag" -> protection.residenceFlag(caster,
|
||
subject == null ? caster.getLocation() : subject.getLocation(), check.argument("", "flag", "f"));
|
||
default -> conditions.check(check, subject instanceof Player player ? player : caster, item).success();
|
||
};
|
||
if (!passed) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private Collection<SkillDefinition> skills(ItemStack item, String slot) {
|
||
if (item == null || item.getType().isAir()) return List.of();
|
||
Set<String> names = new LinkedHashSet<>(itemCodec.skills(item));
|
||
String display = item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? strip(item.getItemMeta().getDisplayName()) : "";
|
||
List<String> lore = item.hasItemMeta() && item.getItemMeta().hasLore() && item.getItemMeta().getLore() != null
|
||
? item.getItemMeta().getLore().stream().map(this::strip).toList() : List.of();
|
||
String mythicType = NBT.get(item, nbt -> { return nbt.hasTag("MYTHIC_TYPE") ? nbt.getString("MYTHIC_TYPE") : ""; });
|
||
for (SkillDefinition skill : registry.all()) {
|
||
if (skill.lore() != null && lore.stream().anyMatch(line -> line.startsWith(strip(skill.lore())))) names.add(skill.name());
|
||
if (skill.itemName() != null && display.equals(strip(skill.itemName()))) names.add(skill.name());
|
||
if (skill.mythicType() != null && skill.mythicType().equalsIgnoreCase(mythicType)) names.add(skill.name());
|
||
}
|
||
List<SkillDefinition> found = new ArrayList<>();
|
||
for (String name : names) registry.find(name).ifPresent(skill -> {
|
||
if (skill.slots().isEmpty() || skill.slots().stream().anyMatch(value -> slotMatches(value, slot))) found.add(skill);
|
||
});
|
||
return found;
|
||
}
|
||
|
||
private List<EquippedItem> equipment(Player player) {
|
||
return List.of(new EquippedItem(player.getInventory().getItemInMainHand(), "mainhand"),
|
||
new EquippedItem(player.getInventory().getItemInOffHand(), "offhand"),
|
||
new EquippedItem(player.getInventory().getHelmet(), "head"),
|
||
new EquippedItem(player.getInventory().getChestplate(), "chest"),
|
||
new EquippedItem(player.getInventory().getLeggings(), "legs"),
|
||
new EquippedItem(player.getInventory().getBoots(), "feet"));
|
||
}
|
||
|
||
private boolean slotMatches(String configured, String actual) {
|
||
String value = configured.toLowerCase(Locale.ROOT);
|
||
return value.equals(actual) || value.equals("all") || value.equals("any")
|
||
|| value.equals("hand") && actual.equals("mainhand")
|
||
|| value.equals("helmet") && actual.equals("head")
|
||
|| value.equals("chestplate") && actual.equals("chest")
|
||
|| value.equals("leggings") && actual.equals("legs")
|
||
|| value.equals("boots") && actual.equals("feet");
|
||
}
|
||
|
||
private LivingEntity selectedLiving(Player player, String selector, Entity target) {
|
||
Entity selected = selector.equalsIgnoreCase("self") ? player : target;
|
||
return selected instanceof LivingEntity living ? living : null;
|
||
}
|
||
|
||
private double value(String source, Player player, ItemStack item) {
|
||
try { return expressions.evaluate(source, player, item, Map.of()); }
|
||
catch (RuntimeException ignored) { return 0; }
|
||
}
|
||
|
||
private boolean roll(String source, Player player, ItemStack item) {
|
||
return ThreadLocalRandom.current().nextDouble(100) < value(source, player, item);
|
||
}
|
||
|
||
private boolean debuffed(Entity entity, String type) {
|
||
String key = entity.getUniqueId() + ":" + type.toUpperCase(Locale.ROOT);
|
||
long until = debuffs.getOrDefault(key, 0L);
|
||
if (until <= System.currentTimeMillis()) {
|
||
debuffs.remove(key);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private void tip(Player player, String message) {
|
||
messages.send(player, message);
|
||
}
|
||
|
||
private String strip(String value) {
|
||
String colored = ChatColor.translateAlternateColorCodes('&', value == null ? "" : value);
|
||
return plugin.getConfig().getBoolean("skill-engine-lore-color", false)
|
||
? colored : ChatColor.stripColor(colored);
|
||
}
|
||
|
||
private String normalize(String value) {
|
||
return value.toLowerCase(Locale.ROOT).replace("_", "").replace("-", "");
|
||
}
|
||
|
||
private void replaceMatchingItem(Player player, ItemStack original, ItemStack changed) {
|
||
for (EquipmentSlot slot : EquipmentSlot.values()) {
|
||
ItemStack equipped = player.getInventory().getItem(slot);
|
||
if (equipped == original) {
|
||
player.getInventory().setItem(slot, changed);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
private Player attackingPlayer(Entity damager) {
|
||
if (damager instanceof Player player) return player;
|
||
if (damager instanceof Projectile projectile && projectile.getShooter() instanceof Player player) return player;
|
||
return null;
|
||
}
|
||
|
||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false)
|
||
public void onDamage(EntityDamageEvent event) {
|
||
if (debuffed(event.getEntity(), "HEAL")) { }
|
||
if (event.getEntity() instanceof Player victim) {
|
||
List<String> info = new ArrayList<>();
|
||
info.add(event.getCause().name());
|
||
Entity combatTarget = event instanceof EntityDamageByEntityEvent by ? by.getDamager() : null;
|
||
if (event instanceof EntityDamageByEntityEvent by) {
|
||
Entity damager = by.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof Entity shooter
|
||
? shooter : by.getDamager();
|
||
info.add(damager.getType().name());
|
||
trigger(victim, TriggerType.DAMAGED_ENTITY, List.of(damager.getType().name()), damager, victim.getLocation(), event);
|
||
if (by.getDamager() instanceof Projectile projectile)
|
||
trigger(victim, TriggerType.DAMAGED_PROJECTILE, List.of(damager.getType().name()), damager, victim.getLocation(), event);
|
||
} else trigger(victim, TriggerType.DAMAGED_BLOCK, List.of(event.getCause().name()), null, victim.getLocation(), event);
|
||
trigger(victim, TriggerType.DAMAGED, info, combatTarget, victim.getLocation(), event);
|
||
trigger(victim, TriggerType.COMBAT, info, combatTarget, victim.getLocation(), event);
|
||
}
|
||
if (event instanceof EntityDamageByEntityEvent by) {
|
||
Player attacker = attackingPlayer(by.getDamager());
|
||
if (attacker != null) {
|
||
if (debuffed(attacker, "ATTACK")) { event.setCancelled(true); return; }
|
||
Entity target = by.getEntity();
|
||
trigger(attacker, TriggerType.ATTACK, List.of(target.getType().name()), target, target.getLocation(), event);
|
||
trigger(attacker, TriggerType.COMBAT, List.of(target.getType().name()), target, target.getLocation(), event);
|
||
if (by.getDamager() instanceof Projectile projectile) {
|
||
TriggerType projectileTrigger = switch (projectile.getType()) {
|
||
case ARROW, SPECTRAL_ARROW -> TriggerType.BOW_HIT;
|
||
case TRIDENT -> TriggerType.TRIDENT_ATTACK;
|
||
case SNOWBALL -> TriggerType.SNOWBALL_ATTACK;
|
||
case FIREBALL, SMALL_FIREBALL -> TriggerType.FIREBALL_ATTACK;
|
||
default -> TriggerType.PROJECTILE_ATTACK;
|
||
};
|
||
trigger(attacker, projectileTrigger, List.of(target.getType().name()), target, target.getLocation(), event);
|
||
}
|
||
}
|
||
Entity directDamager = by.getDamager() instanceof Projectile projectile
|
||
&& projectile.getShooter() instanceof Entity shooter ? shooter : by.getDamager();
|
||
if (attacker == null && debuffed(directDamager, "ATTACK")) event.setCancelled(true);
|
||
}
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onDeath(EntityDeathEvent event) {
|
||
Player killer = event.getEntity().getKiller();
|
||
if (killer != null) {
|
||
trigger(killer, TriggerType.KILL, List.of(event.getEntityType().name()), event.getEntity(), event.getEntity().getLocation(), event);
|
||
trigger(killer, TriggerType.KILL_ENTITY, List.of(event.getEntityType().name()), event.getEntity(), event.getEntity().getLocation(), event);
|
||
if (event.getEntity() instanceof Player)
|
||
trigger(killer, TriggerType.KILL_PLAYER, List.of(), event.getEntity(), event.getEntity().getLocation(), event);
|
||
}
|
||
if (event instanceof PlayerDeathEvent playerDeath) {
|
||
trigger(playerDeath.getEntity(), TriggerType.DEATH, List.of(), playerDeath.getEntity(), playerDeath.getEntity().getLocation(), event);
|
||
trigger(playerDeath.getEntity(), TriggerType.DESPAWN, List.of(), playerDeath.getEntity(), playerDeath.getEntity().getLocation(), event);
|
||
}
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onInteract(PlayerInteractEvent event) {
|
||
Player player = event.getPlayer();
|
||
Action action = event.getAction();
|
||
boolean right = action.isRightClick();
|
||
if (!right) return;
|
||
if (debuffed(player, "USE")) { event.setCancelled(true); return; }
|
||
trigger(player, player.isSneaking() ? TriggerType.SHIFT_USE : TriggerType.USE,
|
||
List.of(), null, event.getClickedBlock() == null ? null : event.getClickedBlock().getLocation(), event);
|
||
if (event.getClickedBlock() == null)
|
||
trigger(player, player.isSneaking() ? TriggerType.SHIFT_RIGHTCLICK : TriggerType.RIGHT_CLICK,
|
||
List.of(), null, player.getLocation(), event);
|
||
}
|
||
|
||
@EventHandler
|
||
public void onAnimation(PlayerAnimationEvent event) {
|
||
if (event.getAnimationType() != PlayerAnimationType.ARM_SWING) return;
|
||
Player player = event.getPlayer();
|
||
trigger(player, player.isSneaking() ? TriggerType.SHIFT_SWING : TriggerType.SWING,
|
||
List.of(), null, player.getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onMove(PlayerMoveEvent event) {
|
||
Player player = event.getPlayer();
|
||
if (debuffed(player, "MOVE") && event.getTo() != null
|
||
&& (event.getFrom().getX() != event.getTo().getX() || event.getFrom().getZ() != event.getTo().getZ())) {
|
||
event.setCancelled(true);
|
||
return;
|
||
}
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onJump(PlayerJumpEvent event) {
|
||
Player player = event.getPlayer();
|
||
if (debuffed(player, "JUMP")) { event.setCancelled(true); return; }
|
||
MagicJumpEvent jump = new MagicJumpEvent(player, event.getFrom(), event.getTo());
|
||
Bukkit.getPluginManager().callEvent(jump);
|
||
if (jump.isCancelled()) { event.setCancelled(true); return; }
|
||
trigger(player, TriggerType.JUMP, List.of(), null, jump.getTo(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onTeleport(PlayerTeleportEvent event) {
|
||
if (debuffed(event.getPlayer(), "TELEPORT")) { event.setCancelled(true); return; }
|
||
trigger(event.getPlayer(), TriggerType.TELEPORT, List.of(), null, event.getTo(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onPlace(BlockPlaceEvent event) {
|
||
if (debuffed(event.getPlayer(), "PLACE")) { event.setCancelled(true); return; }
|
||
trigger(event.getPlayer(), TriggerType.BLOCK_PLACE, List.of(event.getBlockPlaced().getType().name()), null, event.getBlockPlaced().getLocation(), event);
|
||
trigger(event.getPlayer(), TriggerType.BLOCK, List.of(event.getBlockPlaced().getType().name()), null, event.getBlockPlaced().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onBreak(BlockBreakEvent event) {
|
||
if (debuffed(event.getPlayer(), "BREAK")) { event.setCancelled(true); return; }
|
||
trigger(event.getPlayer(), TriggerType.BLOCK_BREAK, List.of(event.getBlock().getType().name()), null, event.getBlock().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onConsume(PlayerItemConsumeEvent event) {
|
||
if (debuffed(event.getPlayer(), "CONSUME")) { event.setCancelled(true); return; }
|
||
trigger(event.getPlayer(), TriggerType.CONSUME, List.of(event.getItem().getType().name()), null, event.getPlayer().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onShoot(EntityShootBowEvent event) {
|
||
if (!(event.getEntity() instanceof Player player)) return;
|
||
if (debuffed(player, "SHOOT")) { event.setCancelled(true); return; }
|
||
trigger(player, event.getProjectile().getType().name().equals("TRIDENT") ? TriggerType.TRIDENT_THROW : TriggerType.SHOOT,
|
||
List.of(), event.getProjectile(), event.getProjectile().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onProjectileLaunch(ProjectileLaunchEvent event) {
|
||
ProjectileSource source = event.getEntity().getShooter();
|
||
if (source instanceof Entity entity && debuffed(entity, "SHOOT")) {
|
||
event.setCancelled(true);
|
||
return;
|
||
}
|
||
if (!(source instanceof Player player)) return;
|
||
if (event.getEntity().getType() == org.bukkit.entity.EntityType.TRIDENT)
|
||
trigger(player, TriggerType.TRIDENT_THROW, List.of(), event.getEntity(), event.getEntity().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onPotionSplash(PotionSplashEvent event) {
|
||
if (event.getEntity().getShooter() instanceof Player player)
|
||
trigger(player, TriggerType.SPLASH_POTION, List.of(), event.getEntity(), event.getEntity().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onLingeringPotionSplash(LingeringPotionSplashEvent event) {
|
||
if (event.getEntity().getShooter() instanceof Player player)
|
||
trigger(player, TriggerType.SPLASH_POTION, List.of(), event.getEntity(), event.getEntity().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onProjectileHit(ProjectileHitEvent event) {
|
||
if (!(event.getEntity().getShooter() instanceof Player player)) return;
|
||
if (event.getHitBlock() == null) return;
|
||
Location location = event.getHitBlock().getLocation();
|
||
TriggerType type = event.getEntity().getType().name().equals("TRIDENT") ? TriggerType.TRIDENT_HIT : TriggerType.HIT;
|
||
trigger(player, type, List.of(event.getEntityType().name()), null, location, event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onDrop(PlayerDropItemEvent event) {
|
||
trigger(event.getPlayer(), TriggerType.DROP, List.of(event.getItemDrop().getItemStack().getType().name()),
|
||
event.getItemDrop(), event.getItemDrop().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onTame(EntityTameEvent event) {
|
||
if (event.getOwner() instanceof Player player)
|
||
trigger(player, TriggerType.TAME, List.of(event.getEntityType().name()), event.getEntity(), event.getEntity().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onEntityInteract(PlayerInteractAtEntityEvent event) {
|
||
trigger(event.getPlayer(), TriggerType.INTERACT, List.of(event.getRightClicked().getType().name()),
|
||
event.getRightClicked(), event.getRightClicked().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onFish(PlayerFishEvent event) {
|
||
String information = event.getCaught() == null ? event.getState().name() : event.getCaught().getType().name();
|
||
trigger(event.getPlayer(), TriggerType.FISH, List.of(information), event.getCaught(), event.getHook().getLocation(), event);
|
||
TriggerType type = switch (event.getState()) {
|
||
case BITE -> TriggerType.FISH_BITE;
|
||
case CAUGHT_FISH -> TriggerType.FISH_CATCH_FISH;
|
||
case CAUGHT_ENTITY -> TriggerType.FISH_CATCH_ENTITY;
|
||
case IN_GROUND -> TriggerType.FISH_GROUND;
|
||
case REEL_IN -> TriggerType.FISH_REEL;
|
||
case FAILED_ATTEMPT -> TriggerType.FISH_FAIL;
|
||
default -> null;
|
||
};
|
||
if (type != null) trigger(event.getPlayer(), type, List.of(information), event.getCaught(), event.getHook().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onSneak(PlayerToggleSneakEvent event) {
|
||
trigger(event.getPlayer(), event.isSneaking() ? TriggerType.CROUCH : TriggerType.UNCROUCH,
|
||
List.of(), null, event.getPlayer().getLocation(), event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onRegain(EntityRegainHealthEvent event) {
|
||
if (debuffed(event.getEntity(), "HEAL")) event.setCancelled(true);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onEntityTeleport(EntityTeleportEvent event) {
|
||
if (debuffed(event.getEntity(), "TELEPORT")) event.setCancelled(true);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = false)
|
||
public void onFlight(PlayerToggleFlightEvent event) {
|
||
if (event.isFlying() && debuffed(event.getPlayer(), "FLY")) event.setCancelled(true);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onArmorChange(PlayerArmorChangeEvent event) {
|
||
equipmentChange(event.getPlayer(), event.getSlotType().name().toLowerCase(Locale.ROOT),
|
||
event.getOldItem(), event.getNewItem(), MagicEquipmentChangeEvent.Cause.ARMOR, event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onHeld(PlayerItemHeldEvent event) {
|
||
Player player = event.getPlayer();
|
||
equipmentChange(player, "mainhand", player.getInventory().getItem(event.getPreviousSlot()),
|
||
player.getInventory().getItem(event.getNewSlot()), MagicEquipmentChangeEvent.Cause.HELD, event);
|
||
}
|
||
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onSwap(PlayerSwapHandItemsEvent event) {
|
||
Player player = event.getPlayer();
|
||
equipmentChange(player, "mainhand", player.getInventory().getItemInMainHand(), event.getMainHandItem(),
|
||
MagicEquipmentChangeEvent.Cause.SWAP, event);
|
||
equipmentChange(player, "offhand", player.getInventory().getItemInOffHand(), event.getOffHandItem(),
|
||
MagicEquipmentChangeEvent.Cause.SWAP, event);
|
||
}
|
||
|
||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||
public void onInventoryClick(InventoryClickEvent event) {
|
||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||
ItemStack mainHand = snapshot(player.getInventory().getItemInMainHand());
|
||
ItemStack offHand = snapshot(player.getInventory().getItemInOffHand());
|
||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||
ItemStack currentMainHand = snapshot(player.getInventory().getItemInMainHand());
|
||
ItemStack currentOffHand = snapshot(player.getInventory().getItemInOffHand());
|
||
if (!sameItem(mainHand, currentMainHand))
|
||
equipmentChange(player, "mainhand", mainHand, currentMainHand,
|
||
MagicEquipmentChangeEvent.Cause.INVENTORY, event);
|
||
if (!sameItem(offHand, currentOffHand))
|
||
equipmentChange(player, "offhand", offHand, currentOffHand,
|
||
MagicEquipmentChangeEvent.Cause.INVENTORY, event);
|
||
});
|
||
}
|
||
|
||
@EventHandler
|
||
public void onBreak(PlayerItemBreakEvent event) {
|
||
if (isArmor(event.getBrokenItem().getType())) return;
|
||
equipmentChange(event.getPlayer(), slotFor(event.getPlayer(), event.getBrokenItem()), event.getBrokenItem(),
|
||
null, MagicEquipmentChangeEvent.Cause.BREAK, event);
|
||
}
|
||
|
||
@EventHandler
|
||
public void onJoin(PlayerJoinEvent event) {
|
||
Bukkit.getScheduler().runTask(plugin, () -> trigger(event.getPlayer(), TriggerType.SPAWN, List.of(), null, event.getPlayer().getLocation(), event));
|
||
}
|
||
|
||
@EventHandler
|
||
public void onRespawn(PlayerRespawnEvent event) {
|
||
Bukkit.getScheduler().runTask(plugin, () -> trigger(event.getPlayer(), TriggerType.SPAWN, List.of(), null, event.getRespawnLocation(), event));
|
||
}
|
||
|
||
@EventHandler
|
||
public void onQuit(PlayerQuitEvent event) {
|
||
trigger(event.getPlayer(), TriggerType.DESPAWN, List.of(), null, event.getPlayer().getLocation(), event);
|
||
cooldowns.remove(event.getPlayer().getUniqueId());
|
||
durations.remove(event.getPlayer().getUniqueId());
|
||
String prefix = event.getPlayer().getUniqueId() + ":";
|
||
counters.keySet().removeIf(key -> key.startsWith(prefix));
|
||
debuffs.keySet().removeIf(key -> key.startsWith(prefix));
|
||
}
|
||
|
||
private void equipmentChange(Player player, String slot, ItemStack oldItem, ItemStack newItem,
|
||
MagicEquipmentChangeEvent.Cause cause, Event source) {
|
||
MagicEquipmentChangeEvent event = new MagicEquipmentChangeEvent(player, slot, oldItem, newItem, cause);
|
||
Bukkit.getPluginManager().callEvent(event);
|
||
if (event.isCancelled()) return;
|
||
if (oldItem != null && !oldItem.getType().isAir())
|
||
triggerItem(player, oldItem, slot, TriggerType.ARMOR_UNEQUIP, List.of(), null, player.getLocation(), source);
|
||
if (newItem != null && !newItem.getType().isAir())
|
||
triggerItem(player, newItem, slot, TriggerType.ARMOR_EQUIP, List.of(), null, player.getLocation(), source);
|
||
}
|
||
|
||
private ItemStack snapshot(ItemStack item) {
|
||
return item == null || item.getType().isAir() ? null : item.clone();
|
||
}
|
||
|
||
private boolean sameItem(ItemStack first, ItemStack second) {
|
||
return first == null ? second == null : first.equals(second);
|
||
}
|
||
|
||
private String slotFor(Player player, ItemStack item) {
|
||
for (EquipmentSlot slot : EquipmentSlot.values())
|
||
if (player.getInventory().getItem(slot) == item
|
||
|| player.getInventory().getItem(slot) != null
|
||
&& player.getInventory().getItem(slot).isSimilar(item))
|
||
return slot.name().toLowerCase(Locale.ROOT);
|
||
return "mainhand";
|
||
}
|
||
|
||
private boolean isArmor(Material material) {
|
||
return material.name().endsWith("_HELMET") || material.name().endsWith("_CHESTPLATE")
|
||
|| material.name().endsWith("_LEGGINGS") || material.name().endsWith("_BOOTS")
|
||
|| material == Material.ELYTRA;
|
||
}
|
||
|
||
private record EquippedItem(ItemStack item, String slot) { }
|
||
private record EventContext(Event event, Location location) { }
|
||
|
||
private record CounterState(long count, long lastUsed) { }
|
||
}
|