如何在bukkit插件中查看名为setCancelled()的类/插件?

时间:2017-03-03 04:41:01

标签: java bukkit

我的bukkit / spigot插件中有一个自定义事件,它扩展PlayerInteractEvent,试图在玩家周围的附近区域打开箱子。

目前,该代码使用此事件来确保没有其他插件(例如,悲伤预防)对玩家能够打开胸部。如果玩家可以打开胸部,我的插件会尝试将物品放入胸部。如果某个插件(理想情况下)或类(作为解决方法)调用它,我想忽略setCancelled()

this question我可以看到要获得我可以使用的课程

String callerClassName = new Exception().getStackTrace()[1].getClassName();
String calleeClassName = new Exception().getStackTrace()[0].getClassName();

获取类名。或者,我可以使用此调用的东西:

StackTraceElement[] stElements = Thread.currentThread().getStackTrace();

但是,关于该问题的所有评论都指出,除了这样做之外,还有更好的方法可以做到这一点。

Bukkit有没有更好的方法呢?

作为参考,这是我的自定义播放器互动事件的全部内容:

public class FakePlayerInteractEvent extends PlayerInteractEvent {
    public FakePlayerInteractEvent(Player player, Action rightClickBlock, ItemStack itemInHand, Block clickedBlock, BlockFace blockFace) {
        super(player, rightClickBlock, itemInHand, clickedBlock, blockFace);
    }
}

围绕事件使用的代码:

PlayerInteractEvent fakeEvent = AutomaticInventory.getInstance().new FakePlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, player.getInventory().getItemInMainHand(), block, BlockFace.UP);
Bukkit.getServer().getPluginManager().callEvent(fakeEvent);
if(!fakeEvent.isCancelled()){ ... do stuff }

2 个答案:

答案 0 :(得分:1)

很棒的问题!暂时让我忽略了刺激的原因 这个问题。 Bukkit没有“发布”确定的方法 事件取消源。但是,你的“评估”方法 一个事件在正确的轨道上。

正如您已经知道或怀疑的那样,使用堆栈跟踪不是一个好的解决方案。 它们生成和描述特定于实现的成本相对较高 细节可能不一定保证保持不变。一个更好的 方法是模仿Bukkit在调用时使用的事件触发过程 callEvent()

虽然不能保证事件触发过程的实现 Bukkit API已经稳定了很多年并且没有改变 许多。这对我们来说是过去5年的工作,只需要一个未成年人 将callEvent()分为callEvent() / fireEvent()时重构。

我希望我可以给你整个EventUtils助手班,但是我 由于版权问题,我不得不对其进行编辑。我确实验证了这一点 减少班级通过适当的单元测试。你或其他任何人 可以免费使用此代码。其评论解释了在中的运作 更多细节。我应该注意,我们使用Doxygen,而不是JavaDoc 文档生成。

public class EventUtils {

    /**
     * @brief Determine if the given event will be cancelled.
     * 
     * This method emulates Bukkit's SimplePluginManager.fireEvent() to evaluate whether it will
     * be cancelled. This is preferred over using callEvent() as this method can limit the scope
     * of evaluation to only plugins of interest. Furthermore, this method will terminate as soon
     * as the event is cancelled to minimize any *side effects* from plugins further down the event
     * chain (e.g. mcMMO). No evaluation will be performed for events that do not
     * implement Cancellable.
     * 
     * The given plugin set is interpreted as either an Allow set or a Deny set, as follows:
     * 
     * - \c allowDeny = \c false - Allow mode. Only enabled plugins included in the given plugin
     *   set will be evaluated.
     * - \c allowDeny = \c false - Deny mode. Only enabled plugins *not* included in the given
     *   plugin set will be evaluated.
     * 
     * @warning Care should be taken when using this method from within a plugin's event handler for
     * the same event or event type (e.g. "faked" events). As this may result in an unending
     * recursion that will crash the server. To prevent this situation, the event handler should
     * (given in order of preference): 1) restrict evaluation to a specific Allow set not including
     * its own plugin; or, 2) add its own plugin to a Deny set. See overloaded convenience methods
     * for more details.
     * 
     * @param evt event under test
     * @param plugins Allow/Deny plugin set
     * @param allowDeny \c false - evaluate using an Allow set; or \c true - evaluate using a
     *        Deny set.
     * @return first plugin that cancelled given event; or \c if none found/did
     */

    public static Plugin willCancel( Event evt, Set<Plugin> plugins, boolean allowDeny ) {
        PluginManager piMgr = Bukkit.getPluginManager();

        /*
         * 1. From SimplePluginManager.callEvent(). Check thread-safety and requirements as if this
         * were a normal event call.
         */
        if ( evt.isAsynchronous() ) {
            if ( Thread.holdsLock( piMgr ) ) {
                throw new IllegalStateException( evt.getEventName()
                        + " cannot be triggered asynchronously from inside synchronized code." );
            }
            if ( Bukkit.isPrimaryThread() ) {
                throw new IllegalStateException( evt.getEventName()
                        + " cannot be triggered asynchronously from primary server thread." );
            }
            return fireUntilCancelled( evt, plugins, allowDeny );
        }
        else {
            synchronized ( piMgr ) {
                return fireUntilCancelled( evt, plugins, allowDeny );
            }
        }

    }


    /**
     * @brief See willCancel() for details.
     * 
     * @note Scoped as `protected` method for unit testing without reflection.
     * 
     * @param evt event under test
     * @param plugins Allow/Deny plugin set
     * @param allowDeny \c false - evaluate using an Allow set; or \c true - evaluate using a
     *        Deny set.
     * @return first plugin that cancelled given event; or \c if none found/did
     */
    protected static Plugin fireUntilCancelled( Event evt, Set<Plugin> plugins, boolean allowDeny ) {

        /*
         * 1. If event cannot be canceled, nothing will cancel it.
         */

        if ( !(evt instanceof Cancellable) )
            return null;

        /*
         * 2. Iterate over the event's "baked" event handler list.
         */

        HandlerList handlers = evt.getHandlers();
        for ( RegisteredListener l : handlers.getRegisteredListeners() ) {

            /*
             * A. Is associated plugin applicable? If not, move to next listener.
             */

            if ( !ofInterest( l.getPlugin(), plugins, allowDeny ) )
                continue;

            /*
             * B. Call registered plugin listener. If event is marked cancelled afterwards, return
             * reference to canceling plugin.
             */

            try {
                l.callEvent( evt );
                if ( ((Cancellable) evt).isCancelled() )
                    return l.getPlugin();
            }
            catch ( EventException e ) {

                /*
                 * Can be safely ignored as it is only used to nag developer about legacy events
                 * and similar matters.
                 */
            }
        }
        return null;
    }


    /**
     * @brief Determine whether the given plugin is of interest.
     * 
     * This method determines whether the given plugin is of interest. A plugin is of no interest
     * if any of the following conditions are met:
     * 
     * - the plugin is disabled
     * - \c allowDeny is \c false (allow) and set does not contains plugin
     * - \c allowDeny is \c true (deny) and set contains plugin
     * 
     * @note Scoped as `protected` method for unit testing without reflection.
     * 
     * @param plugin plugin to evaluate
     * @param plugins plugin allow/deny set
     * @param allowDeny \c false validate against allow set; \c true validate against deny set
     * @return \c true plugin is of interest; \c false otherwise
     */

    protected static boolean ofInterest( Plugin plugin, Set<Plugin> plugins, boolean allowDeny ) {
        if ( !plugin.isEnabled() )
            return false;

        return allowDeny ^ plugins.contains( plugin );
    }
}

答案 1 :(得分:0)

我建议使用优先级

优先顺序如下:

  1. 最低的
  2. LOW
  3. 正常
  4. HIGH
  5. HIGHEST
  6. MONITOR
  7. 如果您将事件优先级设置为HIGHESTMONITOR,则您的事件将在所有其他优先级都已收听后监听事件。例如,即使另一个插件尝试取消该事件,您仍然可以收听该事件。

    注意:建议不要更改MONITOR优先级的事件结果。它应该仅用于监控。

    更改事件优先级(默认值:NORMAL)

    @EventHandler (priority = EventPriority.HIGHEST)
    public void onEvent(Event e) {
    }
    

    如果您希望其他插件在最后一个插件后执行操作,例如,如果您希望World Edit比插件“更强”,请将优先级设置为LOW或{{1} }。如果您希望您的插件获得最终发言权,请提高该优先级。

    <强> @EDIT

    如果你想在没有优先权的情况下这样做,并且实际上需要识别取消插件,Comprehenix from bukkit forums have a solution for you。请记住,不推荐

    关于如何做的示例:

    LOWEST

    你可以找到CancellationDetector的git here