#include "EventAPI.h"
#include "McAPI.h"
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <string>
#include <sstream>
#include <exception>
#include <cstdarg>
#include <Global.hpp>
#include <Engine/TimeTaskSystem.h>
#include <Engine/EngineOwnData.h>
#include <Engine/GlobalShareData.h>
#include <Engine/LocalShareData.h>
#include <BuiltinCommands.h>
#include "APIHelp.h"
#include <NodeJsHelper.h>
#include "BaseAPI.h"
#include "BlockAPI.h"
#include "GuiAPI.h"
#include "CommandAPI.h"
#include "ItemAPI.h"
#include "EntityAPI.h"
#include "PlayerAPI.h"
#include <Loader.h>
#include <Configs.h>
#include <EventAPI.h>
#include <ScheduleAPI.h>
#include <MC/Player.hpp>
#include <MC/BlockSource.hpp>
#include <MC/Actor.hpp>
#include <MC/ActorDamageSource.hpp>
#include <MC/MobEffectInstance.hpp>
#include <MC/HashedString.hpp>
#include <MC/Objective.hpp>
#include <MC/BlockInstance.hpp>
#include <MC/Block.hpp>
#include <MC/VanillaBlocks.hpp>
#include <MC/ItemStack.hpp>
using namespace std;

//////////////////// Listeners ////////////////////

enum class EVENT_TYPES : int {
    /* Player Events */
    onPreJoin = 0,
    onJoin,
    onLeft,
    onRespawn,
    onPlayerDie,
    onPlayerCmd,
    onChat,
    onChangeDim,
    onJump,
    onSneak,
    onAttackEntity,
    onAttackBlock,
    onUseItem,
    onUseItemOn,
    onTakeItem,
    onDropItem,
    onEat,
    onConsumeTotem,
    onEffectAdded,
    onEffectUpdated,
    onEffectRemoved,
    onStartDestroyBlock,
    onDestroyBlock,
    onPlaceBlock,
    afterPlaceBlock,
    onOpenContainer,
    onCloseContainer,
    onInventoryChange,
    onMove,
    onChangeSprinting,
    onSetArmor,
    onUseRespawnAnchor,
    onOpenContainerScreen,
    onExperienceAdd,
    onBedEnter,
    /* Entity Events */
    onMobDie,
    onMobHurt,
    onEntityExplode,
    onProjectileHitEntity,
    onWitherBossDestroy,
    onRide,
    onStepOnPressurePlate,
    onSpawnProjectile,
    onProjectileCreated,
    onNpcCmd,
    onChangeArmorStand,
    onEntityTransformation,
    /* Block Events */
    onBlockInteracted,
    onBlockChanged,
    onBlockExplode,
    onRespawnAnchorExplode,
    onBlockExploded,
    onFireSpread,
    onCmdBlockExecute,
    onContainerChange,
    onProjectileHitBlock,
    onRedStoneUpdate,
    onHopperSearchItem,
    onHopperPushOut,
    onPistonTryPush,
    onPistonPush,
    onFarmLandDecay,
    onUseFrameBlock,
    onLiquidFlow,
    /* Other Events */
    onScoreChanged,
    onTick,
    onServerStarted,
    onConsoleCmd,
    onConsoleOutput,
    /* Economic Events */
    onMoneyAdd,
    onMoneyReduce,
    onMoneyTrans,
    onMoneySet,
    beforeMoneyAdd,
    beforeMoneyReduce,
    beforeMoneyTrans,
    beforeMoneySet,
    onFormResponsePacket,
    /* Outdated Events */
    onAttack,
    onExplode,
    onBedExplode,
    onMobSpawn,
    onContainerChangeSlot,
    EVENT_COUNT
};

struct ListenerListType {
    ScriptEngine* engine;
    script::Global<Function> func;
};

//监听器表
static std::list<ListenerListType> listenerList[int(EVENT_TYPES::EVENT_COUNT)];

//监听器历史
static bool hasListened[int(EVENT_TYPES::EVENT_COUNT)] = {0};

//监听器异常拦截
string EventTypeToString(EVENT_TYPES e) {
    return string(magic_enum::enum_name(e));
}

#define LISTENER_CATCH(TYPE)                                         \
    catch (const Exception& e) {                                     \
        logger.error("Event Callback Failed!");                      \
        logger.error << e << logger.endl;                            \
        logger.error("In Event: " + EventTypeToString(TYPE));        \
        logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \
    }                                                                \
    catch (const std::exception& e) {                                \
        logger.error("Event Callback Failed!");                      \
        logger.error("C++ Uncaught Exception Detected!");            \
        logger.error(TextEncoding::toUTF8(e.what()));                \
        PrintScriptStackTrace();                                     \
        logger.error("In Event: " + EventTypeToString(TYPE));        \
        logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \
    }                                                                \
    catch (const seh_exception& e) {                                 \
        logger.error("Event Callback Failed!");                      \
        logger.error("SEH Uncaught Exception Detected!");            \
        logger.error(TextEncoding::toUTF8(e.what()));                \
        PrintScriptStackTrace();                                     \
        logger.error("In Event: " + EventTypeToString(TYPE));        \
        logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \
    }

//调用事件监听函数，拦截不执行original
#define CallEventRtnVoid(TYPE, ...)                                        \
    std::list<ListenerListType>& nowList = listenerList[int(TYPE)];        \
    bool passToBDS = true;                                                 \
    for (auto& listener : nowList) {                                       \
        EngineScope enter(listener.engine);                                \
        try {                                                              \
            auto result = listener.func.get().call({}, __VA_ARGS__);       \
            if (result.isBoolean() && result.asBoolean().value() == false) \
                passToBDS = false;                                         \
        }                                                                  \
        LISTENER_CATCH(TYPE)                                               \
    }                                                                      \
    if (!passToBDS) {                                                      \
        return;                                                            \
    }

//调用事件监听函数，拦截返回false
#define CallEvent(TYPE, ...)                                               \
    std::list<ListenerListType>& nowList = listenerList[int(TYPE)];        \
    bool passToBDS = true;                                                 \
    for (auto& listener : nowList) {                                       \
        EngineScope enter(listener.engine);                                \
        try {                                                              \
            auto result = listener.func.get().call({}, __VA_ARGS__);       \
            if (result.isBoolean() && result.asBoolean().value() == false) \
                passToBDS = false;                                         \
        }                                                                  \
        LISTENER_CATCH(TYPE)                                               \
    }                                                                      \
    return passToBDS;

//调用事件监听函数，拦截返回RETURN_VALUE
#define CallEventRtnValue(TYPE, RETURN_VALUE, ...)                         \
    std::list<ListenerListType>& nowList = listenerList[int(TYPE)];        \
    bool passToBDS = true;                                                 \
    for (auto& listener : nowList) {                                       \
        EngineScope enter(listener.engine);                                \
        try {                                                              \
            auto result = listener.func.get().call({}, __VA_ARGS__);       \
            if (result.isBoolean() && result.asBoolean().value() == false) \
                passToBDS = false;                                         \
        }                                                                  \
        LISTENER_CATCH(TYPE)                                               \
    }                                                                      \
    if (!passToBDS) {                                                      \
        return RETURN_VALUE;                                               \
    }

//模拟事件调用监听
#define FakeCallEvent(ENGINE, TYPE, ...)                                \
    {                                                                   \
        std::list<ListenerListType>& nowList = listenerList[int(TYPE)]; \
        for (auto& listener : nowList) {                                \
            if (listener.engine == ENGINE) {                            \
                EngineScope enter(listener.engine);                     \
                try {                                                   \
                    listener.func.get().call({}, __VA_ARGS__);          \
                }                                                       \
                LISTENER_CATCH(TYPE)                                    \
            }                                                           \
        }                                                               \
    }

//延迟调用事件
#define CallEventDelayed(TYPE, ...)                                 \
    std::list<ListenerListType>& nowList = listenerList[int(TYPE)]; \
    for (auto& listener : nowList) {                                \
        EngineScope enter(listener.engine);                         \
        try {                                                       \
            NewTimeout(listener.func.get(), {__VA_ARGS__}, 5);      \
        }                                                           \
        LISTENER_CATCH(TYPE)                                        \
    }

//异常检查
#define IF_LISTENED(TYPE)                   \
    if (!listenerList[int(TYPE)].empty()) { \
        try
#define IF_LISTENED_END(TYPE)                                 \
    catch (...) {                                             \
        logger.error("Event Callback Failed!");               \
        logger.error("Uncaught Exception Detected!");         \
        logger.error("In Event: " + EventTypeToString(TYPE)); \
    }                                                         \
    }                                                         \
    return true;


//////////////////// APIs ////////////////////

Local<Value> McClass::listen(const Arguments& args) {
    CHECK_ARGS_COUNT(args, 2);
    CHECK_ARG_TYPE(args[0], ValueKind::kString);
    CHECK_ARG_TYPE(args[1], ValueKind::kFunction);

    try {
        return Boolean::newBoolean(LLSEAddEventListener(EngineScope::currentEngine(), args[0].toStr(), args[1].asFunction()));
    }
    CATCH("Fail to Bind Listener!");
}

//////////////////// Funcs ////////////////////

bool LLSEAddEventListener(ScriptEngine* engine, const string& eventName, const Local<Function>& func) {
    try {
        auto event_enum = magic_enum::enum_cast<EVENT_TYPES>(eventName);
        auto eventId = int(event_enum.value());
        listenerList[eventId].push_back({engine, script::Global<Function>(func)});
        if (!hasListened[eventId]) {
            hasListened[eventId] = true;
            EnableEventListener(eventId);
        }
        return true;
    } catch (...) {
        logger.error("Event \"" + eventName + "\" No Found!\n");
        logger.error("In Plugin: " + ENGINE_GET_DATA(engine)->pluginName);
        return false;
    }
}

bool LLSERemoveAllEventListeners(ScriptEngine* engine) {
    for (auto& listeners : listenerList) {
        listeners.remove_if([engine](auto& listener) {
            return listener.engine == engine;
        });
    }
    return true;
}

bool LLSECallEventsOnHotLoad(ScriptEngine* engine) {
    FakeCallEvent(engine, EVENT_TYPES::onServerStarted);

    auto players = Level::getAllPlayers();
    for (auto& pl : players)
        FakeCallEvent(engine, EVENT_TYPES::onPreJoin, PlayerClass::newPlayer(pl));
    for (auto& pl : players)
        FakeCallEvent(engine, EVENT_TYPES::onJoin, PlayerClass::newPlayer(pl));

    return true;
}

bool LLSECallEventsOnHotUnload(ScriptEngine* engine) {
    auto players = Level::getAllPlayers();
    for (auto& pl : players)
        FakeCallEvent(engine, EVENT_TYPES::onLeft, PlayerClass::newPlayer(pl));
    for (auto& [index, cb] : ENGINE_GET_DATA(engine)->unloadCallbacks) {
        cb(engine);
    }
    ENGINE_GET_DATA(engine)->unloadCallbacks.clear();
    return true;
}

//////////////////// Events ////////////////////

void EnableEventListener(int eventId) {
    using namespace Event;

    switch ((EVENT_TYPES)eventId) {
        case EVENT_TYPES::onJoin:
            Event::PlayerJoinEvent::subscribe([](const PlayerJoinEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onJoin) {
                    CallEvent(EVENT_TYPES::onJoin, PlayerClass::newPlayer((Player*)ev.mPlayer));
                }
                IF_LISTENED_END(EVENT_TYPES::onJoin);
            });
            break;

        case EVENT_TYPES::onPreJoin:
            Event::PlayerPreJoinEvent::subscribe([](const PlayerPreJoinEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onPreJoin) {
                    CallEvent(EVENT_TYPES::onPreJoin, PlayerClass::newPlayer((Player*)ev.mPlayer));
                }
                IF_LISTENED_END(EVENT_TYPES::onPreJoin);
            });
            break;

        case EVENT_TYPES::onLeft:
            Event::PlayerLeftEvent::subscribe([](const PlayerLeftEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onLeft) {
                    CallEvent(EVENT_TYPES::onLeft, PlayerClass::newPlayer((Player*)ev.mPlayer));
                }
                IF_LISTENED_END(EVENT_TYPES::onLeft);
            });
            break;

        case EVENT_TYPES::onChat:
            Event::PlayerChatEvent::subscribe([](const PlayerChatEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onChat) {
                    CallEvent(EVENT_TYPES::onChat, PlayerClass::newPlayer(ev.mPlayer), ev.mMessage);
                }
                IF_LISTENED_END(EVENT_TYPES::onChat);
            });
            break;

        case EVENT_TYPES::onChangeDim:
            Event::PlayerChangeDimEvent::subscribe([](const PlayerChangeDimEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onChangeDim) {
                    CallEvent(EVENT_TYPES::onChangeDim, PlayerClass::newPlayer(ev.mPlayer), Number::newNumber(ev.mToDimensionId)); //======???
                }
                IF_LISTENED_END(EVENT_TYPES::onChangeDim);
            });
            break;

        case EVENT_TYPES::onAttackEntity:
            Event::PlayerAttackEvent::subscribe([](const PlayerAttackEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onAttackEntity) {
                    if (ev.mTarget) {
                        CallEvent(EVENT_TYPES::onAttackEntity, PlayerClass::newPlayer(ev.mPlayer), EntityClass::newEntity(ev.mTarget));
                    }
                }
                IF_LISTENED_END(EVENT_TYPES::onAttackEntity);
            });
            break;

        case EVENT_TYPES::onAttackBlock:
            Event::PlayerAttackBlockEvent::subscribe([](const PlayerAttackBlockEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onAttackBlock) {
                    CallEvent(EVENT_TYPES::onAttackBlock,
                              PlayerClass::newPlayer(ev.mPlayer),
                              BlockClass::newBlock(ev.mBlockInstance),
                              !ev.mItemStack->isNull() ? ItemClass::newItem(ev.mItemStack) : Local<Value>());
                }
                IF_LISTENED_END(EVENT_TYPES::onAttackBlock);
            });
            break;

        case EVENT_TYPES::onPlayerDie:
            Event::PlayerDieEvent::subscribe([](const PlayerDieEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onPlayerDie) {
                    Actor* source = ev.mDamageSource->getEntity();
                    CallEvent(EVENT_TYPES::onPlayerDie, PlayerClass::newPlayer(ev.mPlayer),
                              (source ? EntityClass::newEntity(source) : Local<Value>()));
                }
                IF_LISTENED_END(EVENT_TYPES::onPlayerDie);
            });
            break;

        case EVENT_TYPES::onRespawn:
            Event::PlayerRespawnEvent::subscribe([](const PlayerRespawnEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onRespawn) {
                    CallEvent(EVENT_TYPES::onRespawn, PlayerClass::newPlayer((Player*)ev.mPlayer));
                }
                IF_LISTENED_END(EVENT_TYPES::onRespawn)
            });
            break;

        case EVENT_TYPES::onStartDestroyBlock:
            Event::PlayerStartDestroyBlockEvent::subscribe([](const PlayerStartDestroyBlockEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onStartDestroyBlock) {
                    CallEvent(EVENT_TYPES::onStartDestroyBlock, PlayerClass::newPlayer(ev.mPlayer),
                              BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onStartDestroyBlock);
            });
            break;

        case EVENT_TYPES::onDestroyBlock:
            Event::PlayerDestroyBlockEvent::subscribe([](const PlayerDestroyBlockEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onDestroyBlock) {
                    CallEvent(EVENT_TYPES::onDestroyBlock, PlayerClass::newPlayer(ev.mPlayer), BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onDestroyBlock);
            });
            break;

        case EVENT_TYPES::onPlaceBlock:
            Event::PlayerPlaceBlockEvent::subscribe([](const PlayerPlaceBlockEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onPlaceBlock) {
                    CallEvent(EVENT_TYPES::onPlaceBlock, PlayerClass::newPlayer(ev.mPlayer), BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onPlaceBlock);
            });
            break;

        case EVENT_TYPES::afterPlaceBlock:
            Event::BlockPlacedByPlayerEvent::subscribe([](const BlockPlacedByPlayerEvent& ev) {
                IF_LISTENED(EVENT_TYPES::afterPlaceBlock) {
                    CallEvent(EVENT_TYPES::afterPlaceBlock, PlayerClass::newPlayer(ev.mPlayer), BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::afterPlaceBlock);
                });
            break;

        case EVENT_TYPES::onMove:
            Event::PlayerMoveEvent::subscribe([](const PlayerMoveEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onMove) {
                    CallEvent(EVENT_TYPES::onMove, PlayerClass::newPlayer(ev.mPlayer),
                              FloatPos::newPos(ev.mPos, ev.mPlayer->getDimensionId()));
                }
                IF_LISTENED_END(EVENT_TYPES::onMove);
            });
            break;

        case EVENT_TYPES::onJump:
            Event::PlayerJumpEvent::subscribe([](const PlayerJumpEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onJump) {
                    CallEvent(EVENT_TYPES::onJump, PlayerClass::newPlayer(ev.mPlayer));
                }
                IF_LISTENED_END(EVENT_TYPES::onJump);
            });
            break;

        case EVENT_TYPES::onDropItem:
            Event::PlayerDropItemEvent::subscribe([](const PlayerDropItemEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onDropItem) {
                    CallEvent(EVENT_TYPES::onDropItem, PlayerClass::newPlayer(ev.mPlayer), ItemClass::newItem(ev.mItemStack)); //###### Q lost items ######
                }
                IF_LISTENED_END(EVENT_TYPES::onDropItem);
            });
            break;

        case EVENT_TYPES::onTakeItem:
            Event::PlayerPickupItemEvent::subscribe([](const PlayerPickupItemEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onTakeItem) {
                    CallEvent(EVENT_TYPES::onTakeItem, PlayerClass::newPlayer(ev.mPlayer),
                              EntityClass::newEntity(ev.mItemEntity), ev.mItemStack ? ItemClass::newItem(ev.mItemStack) : Local<Value>());
                }
                IF_LISTENED_END(EVENT_TYPES::onTakeItem);
            });
            break;

        case EVENT_TYPES::onOpenContainer:
            Event::PlayerOpenContainerEvent::subscribe([](const PlayerOpenContainerEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onOpenContainer) {
                    CallEvent(EVENT_TYPES::onOpenContainer, PlayerClass::newPlayer(ev.mPlayer), BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onOpenContainer);
            });
            break;

        case EVENT_TYPES::onCloseContainer:
            Event::PlayerCloseContainerEvent::subscribe([](const PlayerCloseContainerEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onCloseContainer) {
                    CallEvent(EVENT_TYPES::onCloseContainer, PlayerClass::newPlayer(ev.mPlayer), BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onCloseContainer);
            });
            break;

        case EVENT_TYPES::onInventoryChange:
            Event::PlayerInventoryChangeEvent::subscribe([](const PlayerInventoryChangeEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onInventoryChange) {
                    CallEvent(EVENT_TYPES::onInventoryChange, PlayerClass::newPlayer(ev.mPlayer), ev.mSlot,
                              ItemClass::newItem(ev.mPreviousItemStack), ItemClass::newItem(ev.mNewItemStack));
                }
                IF_LISTENED_END(EVENT_TYPES::onInventoryChange);
            });
            break;

        case EVENT_TYPES::onUseItem:
            Event::PlayerUseItemEvent::subscribe([](const PlayerUseItemEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onUseItem) {
                    CallEvent(EVENT_TYPES::onUseItem, PlayerClass::newPlayer((Player*)ev.mPlayer), ItemClass::newItem(ev.mItemStack));
                }
                IF_LISTENED_END(EVENT_TYPES::onUseItem);
            });
            break;

        case EVENT_TYPES::onUseItemOn:
            Event::PlayerUseItemOnEvent::subscribe([](const PlayerUseItemOnEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onUseItemOn) {
                    CallEvent(EVENT_TYPES::onUseItemOn, PlayerClass::newPlayer((Player*)ev.mPlayer),
                              ItemClass::newItem(ev.mItemStack), BlockClass::newBlock(ev.mBlockInstance),
                              Number::newNumber(ev.mFace), FloatPos::newPos(ev.mClickPos));
                }
                IF_LISTENED_END(EVENT_TYPES::onUseItemOn);
            });
            break;

        case EVENT_TYPES::onContainerChange:
            Event::ContainerChangeEvent::subscribe([](const ContainerChangeEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onContainerChange) {
                    CallEvent(EVENT_TYPES::onContainerChange, PlayerClass::newPlayer(ev.mPlayer), BlockClass::newBlock(ev.mBlockInstance),
                              ev.mSlot, ItemClass::newItem(ev.mPreviousItemStack), ItemClass::newItem(ev.mNewItemStack));
                }
                IF_LISTENED_END(EVENT_TYPES::onContainerChange);
            });
            break;

        case EVENT_TYPES::onChangeArmorStand:
            Event::ArmorStandChangeEvent::subscribe([](const ArmorStandChangeEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onChangeArmorStand) {
                    CallEvent(EVENT_TYPES::onChangeArmorStand, EntityClass::newEntity((Actor*)ev.mArmorStand),
                              PlayerClass::newPlayer(ev.mPlayer), Number::newNumber(ev.mSlot));
                }
                IF_LISTENED_END(EVENT_TYPES::onChangeArmorStand);
            });
            break;

        case EVENT_TYPES::onChangeSprinting:
            Event::PlayerSprintEvent::subscribe([](const PlayerSprintEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onChangeSprinting) {
                    CallEvent(EVENT_TYPES::onChangeSprinting, PlayerClass::newPlayer(ev.mPlayer),
                              Boolean::newBoolean(ev.mIsSprinting));
                }
                IF_LISTENED_END(EVENT_TYPES::onChangeSprinting);
            });
            break;

        case EVENT_TYPES::onSneak:
            Event::PlayerSneakEvent::subscribe([](const PlayerSneakEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onSneak) {
                    CallEvent(EVENT_TYPES::onSneak, PlayerClass::newPlayer(ev.mPlayer), Boolean::newBoolean(ev.mIsSneaking));
                }
                IF_LISTENED_END(EVENT_TYPES::onSneak);
            });
            break;

        case EVENT_TYPES::onOpenContainerScreen:
            Event::PlayerOpenContainerScreenEvent::subscribe([](const PlayerOpenContainerScreenEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onOpenContainerScreen) {
                    CallEvent(EVENT_TYPES::onOpenContainerScreen, PlayerClass::newPlayer(ev.mPlayer));
                }
                IF_LISTENED_END(EVENT_TYPES::onOpenContainerScreen);
            });
            break;

        case EVENT_TYPES::onSetArmor:
            Event::PlayerSetArmorEvent::subscribe([](const PlayerSetArmorEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onSetArmor) {
                    CallEvent(EVENT_TYPES::onSetArmor, PlayerClass::newPlayer(ev.mPlayer), Number::newNumber(ev.mSlot),
                              ItemClass::newItem(ev.mArmorItem));
                }
                IF_LISTENED_END(EVENT_TYPES::onSetArmor);
            });
            break;

        case EVENT_TYPES::onEat:
            Event::PlayerEatEvent::subscribe([](const PlayerEatEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onEat) {
                    CallEvent(EVENT_TYPES::onEat, PlayerClass::newPlayer(ev.mPlayer), ItemClass::newItem(ev.mFoodItem));
                }
                IF_LISTENED_END(EVENT_TYPES::onEat);
            });
            break;

        case EVENT_TYPES::onConsumeTotem:
            Event::PlayerConsumeTotemEvent::subscribe([](const PlayerConsumeTotemEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onConsumeTotem) {
                    CallEvent(EVENT_TYPES::onConsumeTotem, PlayerClass::newPlayer(ev.mPlayer));
                }
                IF_LISTENED_END(EVENT_TYPES::onConsumeTotem);
            });
            break;

        case EVENT_TYPES::onEffectAdded:
        case EVENT_TYPES::onEffectRemoved:
        case EVENT_TYPES::onEffectUpdated:
            Event::PlayerEffectChangedEvent::subscribe([](const PlayerEffectChangedEvent& ev) {
                if (ev.mEventType == PlayerEffectChangedEvent::EventType::Add) {
                    IF_LISTENED(EVENT_TYPES::onEffectAdded) {
                        CallEvent(EVENT_TYPES::onEffectAdded, PlayerClass::newPlayer(ev.mPlayer),
                                  String::newString(ev.mEffect->getComponentName().getString()));
                    }
                    IF_LISTENED_END(EVENT_TYPES::onEffectAdded);
                } else if (ev.mEventType == PlayerEffectChangedEvent::EventType::Remove) {
                    IF_LISTENED(EVENT_TYPES::onEffectRemoved) {
                        CallEvent(EVENT_TYPES::onEffectRemoved, PlayerClass::newPlayer(ev.mPlayer),
                                  String::newString(ev.mEffect->getComponentName().getString()));
                    }
                    IF_LISTENED_END(EVENT_TYPES::onEffectRemoved);
                } else if (ev.mEventType == PlayerEffectChangedEvent::EventType::Update) {
                    IF_LISTENED(EVENT_TYPES::onEffectUpdated) {
                        CallEvent(EVENT_TYPES::onEffectUpdated, PlayerClass::newPlayer(ev.mPlayer),
                                  String::newString(ev.mEffect->getComponentName().getString()));
                    }
                    IF_LISTENED_END(EVENT_TYPES::onEffectUpdated);
                }
                return true;
            });
            break;

        case EVENT_TYPES::onUseRespawnAnchor:
            Event::PlayerUseRespawnAnchorEvent::subscribe([](const PlayerUseRespawnAnchorEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onUseRespawnAnchor) {
                    BlockInstance bl(ev.mBlockInstance);
                    CallEvent(EVENT_TYPES::onUseRespawnAnchor, PlayerClass::newPlayer(ev.mPlayer),
                              IntPos::newPos(bl.getPosition(), bl.getDimensionId()));
                }
                IF_LISTENED_END(EVENT_TYPES::onUseRespawnAnchor);
            });
            break;

        case EVENT_TYPES::onRide:
            Event::EntityRideEvent::subscribe([](const EntityRideEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onRide) {
                    CallEvent(EVENT_TYPES::onRide, EntityClass::newEntity(ev.mRider), EntityClass::newEntity(ev.mVehicle));
                }
                IF_LISTENED_END(EVENT_TYPES::onRide);
            });
            break;

        case EVENT_TYPES::onEntityExplode:
            Event::EntityExplodeEvent::subscribe([](const EntityExplodeEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onEntityExplode) {
                    CallEvent(EVENT_TYPES::onEntityExplode, ev.mActor ? EntityClass::newEntity(ev.mActor) : Local<Value>(),
                              FloatPos::newPos(ev.mPos, ev.mDimension->getDimensionId()),
                              Number::newNumber(ev.mRadius), Number::newNumber(ev.mMaxResistance),
                              Boolean::newBoolean(ev.mBreaking), Boolean::newBoolean(ev.mFire));
                }
                IF_LISTENED_END(EVENT_TYPES::onEntityExplode);
            });
            break;

        case EVENT_TYPES::onBlockExplode:
            Event::BlockExplodeEvent::subscribe([](const BlockExplodeEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onBlockExplode) {
                    BlockInstance bl(ev.mBlockInstance);
                    CallEvent(EVENT_TYPES::onBlockExplode, BlockClass::newBlock(bl),
                              IntPos::newPos(bl.getPosition(), bl.getDimensionId()),
                              Number::newNumber(ev.mRadius), Number::newNumber(ev.mMaxResistance),
                              Boolean::newBoolean(ev.mBreaking), Boolean::newBoolean(ev.mFire));
                }
                IF_LISTENED_END(EVENT_TYPES::onBlockExplode);
            });
            break;

        case EVENT_TYPES::onRespawnAnchorExplode:
            Event::BlockExplodeEvent::subscribe([](const BlockExplodeEvent& ev) {
                BlockInstance bl(ev.mBlockInstance);
                if (bl.getBlock() == VanillaBlocks::mRespawnAnchor) {
                    IF_LISTENED(EVENT_TYPES::onRespawnAnchorExplode) {
                        CallEvent(EVENT_TYPES::onRespawnAnchorExplode, IntPos::newPos(bl.getPosition(), bl.getDimensionId()),
                                  Local<Value>());
                    }
                    IF_LISTENED_END(EVENT_TYPES::onRespawnAnchorExplode);
                }
				return true;
            });
            break;

        case EVENT_TYPES::onBlockExploded:
            Event::BlockExplodedEvent::subscribe([](const BlockExplodedEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onBlockExploded) {
                    CallEvent(EVENT_TYPES::onBlockExploded, BlockClass::newBlock(ev.mBlockInstance),
                              EntityClass::newEntity(ev.mExplodeSource));
                }
                IF_LISTENED_END(EVENT_TYPES::onBlockExploded);
            });
            break;

        case EVENT_TYPES::onCmdBlockExecute:
            Event::CmdBlockExecuteEvent::subscribe([](const CmdBlockExecuteEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onCmdBlockExecute) {
                    if (ev.mIsMinecart) {
                        CallEvent(EVENT_TYPES::onCmdBlockExecute, String::newString(ev.mCommand),
                                  FloatPos::newPos(ev.mMinecart->getPosition(), ev.mMinecart->getDimensionId()), true);
                    } else {
                        BlockInstance bl = ev.mBlockInstance;
                        CallEvent(EVENT_TYPES::onCmdBlockExecute, String::newString(ev.mCommand),
                                  FloatPos::newPos(bl.getPosition().toVec3(), bl.getDimensionId()), true);
                    }
                }
                IF_LISTENED_END(EVENT_TYPES::onCmdBlockExecute);
            });
            break;

        case EVENT_TYPES::onRedStoneUpdate:
            Event::RedStoneUpdateEvent::subscribe([](const RedStoneUpdateEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onRedStoneUpdate) {
                    CallEvent(EVENT_TYPES::onRedStoneUpdate, BlockClass::newBlock(ev.mBlockInstance),
                              Number::newNumber(ev.mRedStonePower), Boolean::newBoolean(ev.mIsActivated));
                }
                IF_LISTENED_END(EVENT_TYPES::onRedStoneUpdate);
            });
            break;

        case EVENT_TYPES::onWitherBossDestroy:
            Event::WitherBossDestroyEvent::subscribe([](const WitherBossDestroyEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onWitherBossDestroy) {
                    AABB range = ev.mDestroyRange;
                    int dimId = ((Actor*)ev.mWitherBoss)->getDimensionId();
                    CallEvent(EVENT_TYPES::onWitherBossDestroy, EntityClass::newEntity((Actor*)ev.mWitherBoss),
                              IntPos::newPos(range.min.toBlockPos(), dimId), IntPos::newPos(range.max.toBlockPos(), dimId));
                }
                IF_LISTENED_END(EVENT_TYPES::onWitherBossDestroy);
            });
            break;

        case EVENT_TYPES::onMobHurt:
            Event::MobHurtEvent::subscribe([](const MobHurtEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onMobHurt) {
                    Actor* source = nullptr;
                    if (ev.mDamageSource->isEntitySource()) {
                        if (ev.mDamageSource->isChildEntitySource())
                            source = Level::getEntity(ev.mDamageSource->getEntityUniqueID());
                        else
                            source = Level::getEntity(ev.mDamageSource->getDamagingEntityUniqueID());
                    }
                    CallEvent(EVENT_TYPES::onMobHurt, EntityClass::newEntity(ev.mMob),
                              source ? EntityClass::newEntity(source) : Local<Value>(),
                              float(ev.mDamage), Number::newNumber((int)ev.mDamageSource->getCause()));
                }
                IF_LISTENED_END(EVENT_TYPES::onMobHurt)
            });
            break;

        case EVENT_TYPES::onStepOnPressurePlate:
            Event::EntityStepOnPressurePlateEvent::subscribe([](const EntityStepOnPressurePlateEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onStepOnPressurePlate) {
                    CallEvent(EVENT_TYPES::onStepOnPressurePlate, EntityClass::newEntity(ev.mActor), BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onStepOnPressurePlate);
            });
            break;

        case EVENT_TYPES::onMobDie:
            Event::MobDieEvent::subscribe([](const MobDieEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onMobDie) {
                    Actor* source = nullptr;
                    if (ev.mDamageSource->isEntitySource()) {
                        source = Level::getEntity(ev.mDamageSource->getDamagingEntityUniqueID());
                        if (ev.mDamageSource->isChildEntitySource())
                            source = source->getOwner();
                    }
                    CallEvent(EVENT_TYPES::onMobDie, EntityClass::newEntity((Actor*)ev.mMob),
                              (source ? EntityClass::newEntity(source) : Local<Value>()), Number::newNumber((int)ev.mDamageSource->getCause()));
                }
                IF_LISTENED_END(EVENT_TYPES::onMobDie);
            });
            break;

        case EVENT_TYPES::onNpcCmd:
            Event::NpcCmdEvent::subscribe([](const NpcCmdEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onNpcCmd) {
                    CallEvent(EVENT_TYPES::onNpcCmd, EntityClass::newEntity(ev.mNpc), PlayerClass::newPlayer(ev.mPlayer),
                              String::newString(ev.mCommand));
                }
                IF_LISTENED_END(EVENT_TYPES::onNpcCmd);
            });
            break;

        case EVENT_TYPES::onSpawnProjectile:
            Event::ProjectileSpawnEvent::subscribe([](const ProjectileSpawnEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onSpawnProjectile) {
                    CallEvent(EVENT_TYPES::onSpawnProjectile, EntityClass::newEntity(ev.mShooter), String::newString(ev.mType));
                }
                IF_LISTENED_END(EVENT_TYPES::onSpawnProjectile);
            });
            break;

        case EVENT_TYPES::onProjectileCreated:
            Event::ProjectileCreatedEvent::subscribe([](const ProjectileCreatedEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onProjectileCreated) {
                    CallEvent(EVENT_TYPES::onProjectileCreated, EntityClass::newEntity(ev.mShooter), EntityClass::newEntity(ev.mProjectile));
                }
                IF_LISTENED_END(EVENT_TYPES::onProjectileCreated);
            });
            break;

        case EVENT_TYPES::onProjectileHitEntity:
            Event::ProjectileHitEntityEvent::subscribe([](const ProjectileHitEntityEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onProjectileHitEntity) {
                    CallEvent(EVENT_TYPES::onProjectileHitEntity, EntityClass::newEntity(ev.mTarget), EntityClass::newEntity(ev.mSource));
                }
                IF_LISTENED_END(EVENT_TYPES::onProjectileHitEntity);
            });
            break;

        case EVENT_TYPES::onEntityTransformation:
            Event::EntityTransformEvent::subscribe([](const EntityTransformEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onEntityTransformation) {
                    CallEvent(EVENT_TYPES::onEntityTransformation, String::newString(to_string(ev.mBeforeEntityUniqueId->id)), EntityClass::newEntity(ev.mAfterEntity));
                }
                IF_LISTENED_END(EVENT_TYPES::onEntityTransformation);
            });
            break;

        case EVENT_TYPES::onProjectileHitBlock:
            Event::ProjectileHitBlockEvent::subscribe([](const ProjectileHitBlockEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onProjectileHitBlock) {
                    CallEvent(EVENT_TYPES::onProjectileHitBlock, BlockClass::newBlock(ev.mBlockInstance),
                              EntityClass::newEntity(ev.mSource));
                }
                IF_LISTENED_END(EVENT_TYPES::onProjectileHitBlock);
            });
            break;

        case EVENT_TYPES::onLiquidFlow:
            Event::LiquidSpreadEvent::subscribe([](const LiquidSpreadEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onLiquidFlow) {
                    CallEvent(EVENT_TYPES::onLiquidFlow, BlockClass::newBlock(ev.mBlockInstance), IntPos::newPos(ev.mTarget, ev.mDimensionId));
                }
                IF_LISTENED_END(EVENT_TYPES::onLiquidFlow);
            });
            break;

        case EVENT_TYPES::onUseFrameBlock:
            Event::PlayerUseFrameBlockEvent::subscribe([](const PlayerUseFrameBlockEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onUseFrameBlock) {
                    CallEvent(EVENT_TYPES::onUseFrameBlock, PlayerClass::newPlayer(ev.mPlayer),
                              BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onUseFrameBlock);
            });
            break;

        case EVENT_TYPES::onBlockInteracted:
            Event::BlockInteractedEvent::subscribe([](const BlockInteractedEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onBlockInteracted) {
                    CallEvent(EVENT_TYPES::onBlockInteracted, PlayerClass::newPlayer(ev.mPlayer),
                              BlockClass::newBlock(ev.mBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onBlockInteracted);
            });
            break;

        case EVENT_TYPES::onFarmLandDecay:
            Event::FarmLandDecayEvent::subscribe([](const FarmLandDecayEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onFarmLandDecay) {
                    BlockInstance bl(ev.mBlockInstance);
                    CallEvent(EVENT_TYPES::onFarmLandDecay, IntPos::newPos(bl.getPosition(), bl.getDimensionId()),
                              EntityClass::newEntity(ev.mActor));
                }
                IF_LISTENED_END(EVENT_TYPES::onFarmLandDecay);
            });
            break;

        case EVENT_TYPES::onPistonTryPush:
            Event::PistonTryPushEvent::subscribe([](const PistonTryPushEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onPistonTryPush) {
                    BlockInstance bl(ev.mPistonBlockInstance);
                    CallEvent(EVENT_TYPES::onPistonTryPush, IntPos::newPos(bl.getPosition(), bl.getDimensionId()),
                              BlockClass::newBlock(ev.mTargetBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onPistonTryPush);
            });
            break;

        case EVENT_TYPES::onPistonPush:
            Event::PistonPushEvent::subscribe([](const PistonPushEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onPistonPush) {
                    BlockInstance bl(ev.mPistonBlockInstance);
                    CallEvent(EVENT_TYPES::onPistonPush, IntPos::newPos(bl.getPosition(), bl.getDimensionId()),
                              BlockClass::newBlock(ev.mTargetBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onPistonPush);
            });
            break;

        case EVENT_TYPES::onHopperSearchItem:
            Event::HopperSearchItemEvent::subscribe([](const HopperSearchItemEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onHopperSearchItem) {
                    if (ev.isMinecart) {
                        CallEvent(EVENT_TYPES::onHopperSearchItem, FloatPos::newPos(ev.mMinecartPos, ev.mDimensionId), ev.isMinecart);
                    } else {
                        BlockInstance bl = ev.mHopperBlock;
                        CallEvent(EVENT_TYPES::onHopperSearchItem, FloatPos::newPos(bl.getPosition().toVec3(), ev.mDimensionId), ev.isMinecart);
                    }
                }
                IF_LISTENED_END(EVENT_TYPES::onHopperSearchItem);
            });
            break;

        case EVENT_TYPES::onHopperPushOut:
            Event::HopperPushOutEvent::subscribe([](const HopperPushOutEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onHopperPushOut) {
                    CallEvent(EVENT_TYPES::onHopperPushOut, FloatPos::newPos(ev.mPos, ev.mDimensionId));
                }
                IF_LISTENED_END(EVENT_TYPES::onHopperPushOut);
            });
            break;

        case EVENT_TYPES::onFireSpread:
            Event::FireSpreadEvent::subscribe([](const FireSpreadEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onFireSpread) {
                    CallEvent(EVENT_TYPES::onFireSpread, IntPos::newPos(ev.mTarget, ev.mDimensionId));
                }
                IF_LISTENED_END(EVENT_TYPES::onFireSpread);
            });
            break;

        case EVENT_TYPES::onBlockChanged:
            Event::BlockChangedEvent::subscribe([](const BlockChangedEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onBlockChanged) {
                    CallEvent(EVENT_TYPES::onBlockChanged, BlockClass::newBlock(ev.mPreviousBlockInstance),
                              BlockClass::newBlock(ev.mNewBlockInstance));
                }
                IF_LISTENED_END(EVENT_TYPES::onBlockChanged);
            });
            break;

        case EVENT_TYPES::onScoreChanged:
            Event::PlayerScoreChangedEvent::subscribe([](const PlayerScoreChangedEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onScoreChanged) {
                    CallEvent(EVENT_TYPES::onScoreChanged, PlayerClass::newPlayer(ev.mPlayer),
                              Number::newNumber(ev.mScore), String::newString(ev.mObjective->getName()),
                              String::newString(ev.mObjective->getDisplayName()));
                }
                IF_LISTENED_END(EVENT_TYPES::onScoreChanged);
            });
            break;

        case EVENT_TYPES::onConsoleOutput:
            Event::ConsoleOutputEvent::subscribe([](const ConsoleOutputEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onConsoleOutput) {
                    CallEvent(EVENT_TYPES::onConsoleOutput, String::newString(ev.mOutput));
                }
                IF_LISTENED_END(EVENT_TYPES::onConsoleOutput);
            });
            break;

        case EVENT_TYPES::onMobSpawn:
            Event::MobSpawnEvent::subscribe([](const MobSpawnEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onMobSpawn) {
                    CallEvent(EVENT_TYPES::onMobSpawn, String::newString(ev.mTypeName), FloatPos::newPos(ev.mPos, ev.mDimensionId));
                }
                IF_LISTENED_END(EVENT_TYPES::onMobSpawn);
            });
            break;

        case EVENT_TYPES::onExperienceAdd:
            Event::PlayerExperienceAddEvent::subscribe([](const PlayerExperienceAddEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onExperienceAdd) {
                    CallEvent(EVENT_TYPES::onExperienceAdd, PlayerClass::newPlayer(ev.mPlayer), Number::newNumber(ev.mExp));
                }
                IF_LISTENED_END(EVENT_TYPES::onExperienceAdd);
            });
            break;

        case EVENT_TYPES::onBedEnter:
            Event::PlayerBedEnterEvent::subscribe([](const PlayerBedEnterEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onBedEnter) {
                    BlockInstance bl(*ev.mBlockInstance);
                    CallEvent(EVENT_TYPES::onBedEnter, PlayerClass::newPlayer(ev.mPlayer), IntPos::newPos(bl.getPosition(), bl.getDimensionId()));
                }
                IF_LISTENED_END(EVENT_TYPES::onBedEnter);
            });
            break;

        /* DEPRECATED AND RECENTLY REMOVED - START */

        case EVENT_TYPES::onAttack:
            Event::PlayerAttackEvent::subscribe([](const PlayerAttackEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onAttack) {
                    if (ev.mTarget) {
                        CallEvent(EVENT_TYPES::onAttack, PlayerClass::newPlayer(ev.mPlayer), EntityClass::newEntity(ev.mTarget));
                    }
                }
                IF_LISTENED_END(EVENT_TYPES::onAttack);
            });
            break;

        case EVENT_TYPES::onContainerChangeSlot:
            Event::ContainerChangeEvent::subscribe([](const ContainerChangeEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onContainerChange) {
                    CallEvent(EVENT_TYPES::onContainerChange, PlayerClass::newPlayer(ev.mPlayer), BlockClass::newBlock(ev.mBlockInstance),
                              ev.mSlot, ItemClass::newItem(ev.mPreviousItemStack), ItemClass::newItem(ev.mNewItemStack));
                }
                IF_LISTENED_END(EVENT_TYPES::onContainerChange);
            });
            break;

        case EVENT_TYPES::onExplode:
            Event::EntityExplodeEvent::subscribe([](const EntityExplodeEvent& ev) {
                IF_LISTENED(EVENT_TYPES::onExplode) {
                    CallEvent(EVENT_TYPES::onExplode, ev.mActor ? EntityClass::newEntity(ev.mActor) : Local<Value>(),
                              FloatPos::newPos(ev.mPos, ev.mDimension->getDimensionId()),
                              Number::newNumber(ev.mRadius), Number::newNumber(ev.mMaxResistance),
                              Boolean::newBoolean(ev.mBreaking), Boolean::newBoolean(ev.mFire));
                }
                IF_LISTENED_END(EVENT_TYPES::onExplode);
            });
            break;

        case EVENT_TYPES::onBedExplode:
            Event::BlockExplodeEvent::subscribe([](const BlockExplodeEvent& ev) {
                BlockInstance bl(ev.mBlockInstance);
                if (bl.getBlock()->getTypeName() == "minecraft:bed") {
                    IF_LISTENED(EVENT_TYPES::onBedExplode) {
                        CallEvent(EVENT_TYPES::onBedExplode, IntPos::newPos(bl.getPosition(), bl.getDimensionId()));
                    }
                    IF_LISTENED_END(EVENT_TYPES::onBedExplode);
                }
                return true;
            });
            break;

        /* DEPRECATED AND RECENTLY REMOVED - END */

        default:
            break;
    }
}

void InitBasicEventListeners() {
    using namespace Event;

    Event::PlayerCmdEvent::subscribe([](const PlayerCmdEvent& ev) {
        string cmd = ev.mCommand;
        Player* player = ev.mPlayer;

        vector<string> paras;
        bool isFromOtherEngine = false;
        string prefix = LLSEFindCmdReg(true, cmd, paras, &isFromOtherEngine);

        if (!prefix.empty()) {
            // LLSE Registered Cmd
            int perm = localShareData->playerCmdCallbacks[prefix].perm;

            if (player->getCommandPermissionLevel() >= perm) {
                bool callbackRes = CallPlayerCmdCallback(player, prefix, paras);
                IF_LISTENED(EVENT_TYPES::onPlayerCmd) {
                    CallEvent(EVENT_TYPES::onPlayerCmd, PlayerClass::newPlayer(player), String::newString(cmd));
                }
                IF_LISTENED_END(EVENT_TYPES::onPlayerCmd);
                if (!callbackRes)
                    return false;
            }
        } else {
            if (isFromOtherEngine)
                return false;

            // Other Cmd
            IF_LISTENED(EVENT_TYPES::onPlayerCmd) {
                CallEvent(EVENT_TYPES::onPlayerCmd, PlayerClass::newPlayer(player), String::newString(cmd));
            }
            IF_LISTENED_END(EVENT_TYPES::onPlayerCmd);
        }
        return true;
    });

    Event::ConsoleCmdEvent::subscribe_ref([](ConsoleCmdEvent& ev) {
        string cmd = ev.mCommand;

        // PreProcess
        if (!ProcessDebugEngine(cmd))
            return false;
        if (!ProcessOldHotManageCommand(ev.mCommand))
            return false;
#ifdef LLSE_BACKEND_NODEJS
        if (!NodeJsHelper::processConsoleNpmCmd(ev.mCommand))
            return false;
#endif

        // CallEvents
        vector<string> paras;
        bool isFromOtherEngine = false;
        string prefix = LLSEFindCmdReg(false, cmd, paras, &isFromOtherEngine);

        if (!prefix.empty()) {
            // LLSE Registered Cmd

            bool callbackRes = CallServerCmdCallback(prefix, paras);
            IF_LISTENED(EVENT_TYPES::onConsoleCmd) {
                CallEvent(EVENT_TYPES::onConsoleCmd, String::newString(cmd));
            }
            IF_LISTENED_END(EVENT_TYPES::onConsoleCmd);
            if (!callbackRes)
                return false;
        } else {
            if (isFromOtherEngine)
                return false;

            // Other Cmd
            IF_LISTENED(EVENT_TYPES::onConsoleCmd) {
                CallEvent(EVENT_TYPES::onConsoleCmd, String::newString(cmd));
            }
            IF_LISTENED_END(EVENT_TYPES::onConsoleCmd);
        }
        return true;
    });

    // Plugin Hot Management
    Event::ScriptPluginManagerEvent::subscribe_ref([](ScriptPluginManagerEvent& ev) {
        // if (!LL::isDebugMode())
        //     return false;

        switch (ev.operation) {
            case ScriptPluginManagerEvent::Operation::Load:
                // ev.pluginType is not used
                // since in loadPlugin there will be check
                if (PluginManager::loadPlugin(ev.target, true, true))
                    ev.success = true;
                break;

            case ScriptPluginManagerEvent::Operation::Unload:
                if(PluginManager::unloadPlugin(ev.target))
                    ev.success = true;
                break;

            case ScriptPluginManagerEvent::Operation::Reload:
                if(PluginManager::reloadPlugin(ev.target))
                    ev.success = true;
                break;

            default:
                break;
        }
        if (ev.success)
            return false;   // Success. No need to spread to next engine
        return true;
    });

    // For RegisterCmd...
    Event::RegCmdEvent::subscribe([](const RegCmdEvent& ev) {
        isCmdRegisterEnabled = true;

        //处理延迟注册
        ProcessRegCmdQueue();
        return true;
    });


    // ===== onServerStarted =====
    Event::ServerStartedEvent::subscribe([](Event::ServerStartedEvent ev) {
        IF_LISTENED(EVENT_TYPES::onServerStarted) {
            CallEventDelayed(EVENT_TYPES::onServerStarted);
        }
        IF_LISTENED_END(EVENT_TYPES::onServerStarted);
        return true;
    });
}

inline bool CallTickEvent() {
    IF_LISTENED(EVENT_TYPES::onTick) {
        CallEvent(EVENT_TYPES::onTick);
    }
    IF_LISTENED_END(EVENT_TYPES::onTick);
}

// 植入tick
THook(void, "?tick@ServerLevel@@UEAAXXZ",
      void* _this) {
#ifndef LLSE_BACKEND_NODEJS
    try {
        std::list<ScriptEngine*> tmpList;
        {
            SRWLockSharedHolder lock(globalShareData->engineListLock);
            // low efficiency
            tmpList = globalShareData->globalEngineList;
        }
        for (auto engine : tmpList) {
            if (EngineManager::isValid(engine) && EngineManager::getEngineType(engine) == LLSE_BACKEND_TYPE) {
                EngineScope enter(engine);
                engine->messageQueue()->loopQueue(script::utils::MessageQueue::LoopType::kLoopOnce);
            }
        }
    } catch (...) {
        logger.error("Error occurred in Engine Message Loop!");
        logger.error("Uncaught Exception Detected!");
    }
#endif
    CallTickEvent();
    return original(_this);
}

/* onTurnLectern // 由于还是不能拦截掉书，暂时注释
THook(void, "?handle@ServerNetworkHandler@@UEAAXAEBVNetworkIdentifier@@AEBVLecternUpdatePacket@@@Z",
    ServerNetworkHandler* handler, NetworkIdentifier* id, Packet* pkt)
{
    IF_LISTENED(EVENT_TYPES::onTurnLectern)
    {
        // Packet* pkt = *(Packet**)pPacket;
        Player* player = Raw_GetPlayerFromPacket(handler, id, pkt);
        if (!player)
            return;

        int page = *((DWORD*)pkt + 12);
        dAccess<bool>(pkt, 56) = false;
        bool shouldDropBook = *((BYTE*)pkt + 56);
        int totalPages = *((DWORD*)pkt + 13);
        auto* bp = new BlockPos;
        bp->x = *((DWORD*)pkt + 15);
        bp->y = *((DWORD*)pkt + 16);
        bp->z = *((DWORD*)pkt + 17);
        CallEventRtnVoid(EVENT_TYPES::onTurnLectern, PlayerClass::newPlayer(player), IntPos::newPos(bp, Raw_GetPlayerDimId(player)), page, totalPages, Boolean::newBoolean(shouldDropBook));
    }
    IF_LISTENED_END(EVENT_TYPES::onTurnLectern);
    original(handler,id,pkt);
}
*/

bool MoneyBeforeEventCallback(LLMoneyEvent type, xuid_t from, xuid_t to, money_t value) {
    switch (type) {
        case LLMoneyEvent::Add: {
            IF_LISTENED(EVENT_TYPES::beforeMoneyAdd) {
                CallEvent(EVENT_TYPES::beforeMoneyAdd, String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::beforeMoneyAdd);
            break;
        }
        case LLMoneyEvent::Reduce: {
            IF_LISTENED(EVENT_TYPES::beforeMoneyReduce) {
                CallEvent(EVENT_TYPES::beforeMoneyReduce, String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::beforeMoneyReduce);
            break;
        }
        case LLMoneyEvent::Trans: {
            IF_LISTENED(EVENT_TYPES::beforeMoneyTrans) {
                CallEvent(EVENT_TYPES::beforeMoneyTrans, String::newString(from), String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::beforeMoneyTrans);
            break;
        }
        case LLMoneyEvent::Set: {
            IF_LISTENED(EVENT_TYPES::beforeMoneySet) {
                CallEvent(EVENT_TYPES::beforeMoneySet, String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::beforeMoneySet);
            break;
        }
        default:
            break;
    }
    return true;
}

bool MoneyEventCallback(LLMoneyEvent type, xuid_t from, xuid_t to, money_t value) {
    switch (type) {
        case LLMoneyEvent::Add: {
            IF_LISTENED(EVENT_TYPES::onMoneyAdd) {
                CallEvent(EVENT_TYPES::onMoneyAdd, String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::onMoneyAdd);
            break;
        }
        case LLMoneyEvent::Reduce: {
            IF_LISTENED(EVENT_TYPES::onMoneyReduce) {
                CallEvent(EVENT_TYPES::onMoneyReduce, String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::onMoneyReduce);
            break;
        }
        case LLMoneyEvent::Trans: {
            IF_LISTENED(EVENT_TYPES::onMoneyTrans) {
                CallEvent(EVENT_TYPES::onMoneyTrans, String::newString(from), String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::onMoneyTrans);
            break;
        }
        case LLMoneyEvent::Set: {
            IF_LISTENED(EVENT_TYPES::onMoneySet) {
                CallEvent(EVENT_TYPES::onMoneySet, String::newString(to), Number::newNumber(value));
            }
            IF_LISTENED_END(EVENT_TYPES::onMoneySet);
            break;
        }
        default:
            break;
    }
    return true;
}