package net.t7seven7t.craftfx.trigger; import com.google.common.collect.ImmutableList; import net.t7seven7t.craftfx.CraftFX; import net.t7seven7t.craftfx.data.ConfigData; import net.t7seven7t.craftfx.data.Data; import net.t7seven7t.craftfx.data.generic.EntityTypeData; import net.t7seven7t.craftfx.data.trigger.CooldownData; import net.t7seven7t.craftfx.data.trigger.SlotData; import net.t7seven7t.craftfx.item.ItemDefinition; import org.bukkit.Bukkit; import org.bukkit.entity.EntityType; import org.bukkit.event.Event; import org.bukkit.event.EventException; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.EventExecutor; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; /** * */ public final class TriggerSpec { private final List<String> aliases = new ArrayList<>(); private final List<Predicate<TriggerContext>> predicates = new ArrayList<>(); private final List<Trigger> triggers = new ArrayList<>(); private final List<Data> defaultData = new ArrayList<>(); private final Listener listener = new Listener() { }; private TriggerSpec() { defaultData.add(new ConfigData()); defaultData.add(new CooldownData()); defaultData.add(new SlotData("hand")); defaultData.add(new EntityTypeData()); predicates.add(c -> { final EntityTypeData data = c.getData(EntityTypeData.class).get(); final Optional<EntityType> optType = data.getEntityType(); if (!optType.isPresent()) return true; return c.getTarget().getEntity(optType.get().getEntityClass()).isPresent(); }); // todo: add default filters like level/xp based, etc & make defaults configurable } public static Builder builder() { return new Builder(); } public List<String> getAliases() { return ImmutableList.copyOf(aliases); } private void run(TriggerContext context, Event event) { context.spec = this; // Check all inventory slots specified by SlotData // If any matching run them final ItemStack[] contents = context.getInitiator().getInventory().getContents(); // special use case for where context target is an ItemStack:: final Optional<ItemStack> optItem = context.getTarget().as(ItemStack.class); final int heldSlot = context.getInitiator().getInventory().getHeldItemSlot(); for (Trigger t : triggers) { if (optItem.isPresent()) { // special use for contexts that have a ItemStack target if (!t.getItemDefinition().isSimilar(optItem.get())) continue; } else if (!isItemCorrect(t, contents, heldSlot)) continue; final Optional<Function> optFunc = context.getTarget().as(Function.class); if (optFunc.isPresent()) { // unsafe call if someone uses other functions as target context = context.copy(optFunc.get().apply(event)); } final TriggerContext copy = context.copy(); t.fill(copy); if (predicates.stream().anyMatch(p -> !p.test(copy))) continue; t.run(copy); } } boolean isItemCorrect(Trigger trigger, ItemStack[] contents, int heldSlot) { final SlotData slotData = trigger.getData(SlotData.class).get(); final ItemDefinition item = trigger.getItemDefinition(); if (slotData.isHandSlot() && item.isSimilar(contents[heldSlot])) return true; for (int slot : slotData.getSlots()) { if (slot >= contents.length) continue; if (item.isSimilar(contents[slot])) return true; } return false; } public void addTrigger(Trigger trigger) { triggers.add(trigger); defaultData.forEach(d -> trigger.offer(d.copy())); trigger.setupCooldowns(); } @Override public String toString() { return "TriggerSpec{" + "aliases=" + aliases + '}'; } public static final class Builder { private final TriggerSpec spec = new TriggerSpec(); public TriggerSpec build() { return spec; } public Builder aliases(String... aliases) { for (String alias : aliases) { spec.aliases.add(alias.toLowerCase()); } return Builder.this; } public Builder data(Data data) { Iterator<Data> it = spec.defaultData.iterator(); Class<?> clazz = data.getClass(); while (it.hasNext()) { if (it.next().getClass() == clazz) it.remove(); } spec.defaultData.add(data); return Builder.this; } public <T extends Event> Builder listener(Class<T> eventClazz, Function<T, TriggerContext> function, EventPriority priority) { return listener(eventClazz, function, priority, false); } public <T extends Event> Builder listener(Class<T> eventClazz, Function<T, TriggerContext> function) { return listener(eventClazz, function, EventPriority.MONITOR); } public <T extends Event> Builder listener(Class<T> eventClazz, Function<T, TriggerContext> function, boolean ignoreCancelled) { return listener(eventClazz, function, EventPriority.MONITOR, ignoreCancelled); } public <T extends Event> Builder listener(Class<T> eventClazz, Function<T, TriggerContext> function, EventPriority priority, boolean ignoreCancelled) { Bukkit.getPluginManager().registerEvent(eventClazz, spec.listener, priority, new TriggerExecutor<>(eventClazz, spec, function), CraftFX.plugin(), ignoreCancelled); return Builder.this; } public Builder filter(Predicate<TriggerContext> predicate) { spec.predicates.add(predicate); return Builder.this; } } private static class TriggerExecutor<T extends Event> implements EventExecutor { private final Function<T, TriggerContext> function; private final TriggerSpec spec; private final Class<T> clazz; private TriggerExecutor(Class<T> clazz, TriggerSpec spec, Function<T, TriggerContext> function) { this.clazz = clazz; this.spec = spec; this.function = function; } @Override public void execute(Listener listener, Event event) throws EventException { if (!clazz.isInstance(event)) return; final TriggerContext context = function.apply((T) event); if (context == null) return; if (event.isAsynchronous()) { Bukkit.getScheduler().runTask(CraftFX.plugin(), () -> spec.run(context, event)); return; } spec.run(context, event); } } }