跳到正文
本頁目錄

事件指南

介紹

LeviLamina內置了一套事件系統,提供簡單事件和供模組發佈自己的事件。
所有的內置事件都被放在了ll/api/event/目錄下,並且被分類並放置在command,entity,player,world等子目錄下。

監聽事件

示例: 服務端啟動完成事件

C++
#include "ll/api/event/EventBus.h"
#include "ll/api/event/server/ServerStartedEvent.h"

void ListenEvents() {
    using namespace ll::event;
    EventBus& bus = EventBus::getInstance();
    bus.emplaceListener<ServerStartedEvent>([](ServerStartedEvent& event) {
        // 服务端启动完成后要执行的代码
    });
}

發佈事件

示例: 服務端啟動完成事件

  • 創建一個名為ServerStartedEvent.h的頭文件,然後在其中定義ServerStartedEvent類,該類繼承自ll::event::Event 類。

關於可取消的事件

如果你希望你的事件是可取消的,就繼承ll::event::Cancellable<ll::event::Event>類。

  • 添加該事件的類成員和獲取方法,並通過__declspec(dllexport)導出方法。

關於LLAPI

LeviLamina中的LLAPI宏本質是通過在xmake.lua中使用add_defines("LL_EXPORT")預定義LL_EXPORT 以實現在LeviLamina編譯時LLAPI表示__declspec(dllexport),在其它模組引用時變為__declspec(dllimport) 以供其它模組導入該方法,以下是一個實現這樣的宏的簡單的示例,在實際使用中你需要將MOD_EXPORT和MODAPI 改名以避免與其它模組的宏產生衝突:

C++
#ifdef MOD_EXPORT
#define MODAPI __declspec(dllexport)
#else
#define MODAPI __declspec(dllimport)
#endif

這樣,你只需要在你的模組的xmake.lua中添加add_defines("MOD_EXPORT"),然後在需要被導出的方法前面添加MODAPI 即可導出你的模組的方法。
如果你希望其它模組能夠在不鏈接你的模組的情況下使用你的模組提供的事件,可以直接將事件的成員設為 public,或直接在頭文件中實現獲取方法。另外,最好在事件中聲明並實現一個serialize方法,以方便在控制台中輸出該事件的信息。

C++
#pragma once

#include "ll/api/event/Event.h"

#include "mc/server/ServerInstance.h"

namespace ll::event::inline server {
class ServerStartedEvent final : public Event {
    ServerInstance& mInstance;

public:
    constexpr explicit ServerStartedEvent(ServerInstance& server) : mInstance(server) {}

    LLAPI void serialize(CompoundTag&) const override;

    LLNDAPI ServerInstance& server() const;
};
} // namespace ll::event::inline server
  • 在ServerStartedEvent.cpp中實現ServerStartedEvent類的serialize,server方法。並且在事件應該被觸發時調用 EventBus::publish方法。

關於可取消的事件

如果你的事件是可取消的,你還需要通過判斷Event::isCancelled方法的返回值來決定是否執行取消該事件的操作。示例如下:

C++
LL_TYPE_INSTANCE_HOOK(
    PlayerAttackEventHook,
    HookPriority::Normal,
    Player,
    &Player::_attack,
    bool,
    Actor&                                         ac,
    ::SharedTypes::Legacy::ActorDamageCause const& cause,
    bool                                           doPredictiveSound
) {
    auto ev = PlayerAttackEvent(*this, ac, cause);
    EventBus::getInstance().publish(ev);
    if (ev.isCancelled()) {
        return false;
    }
    return origin(ac, cause, doPredictiveSound);
}
  • 在源文件中定義定義一個類型為std::unique_ptr<EmitterBase>的靜態函數和一個繼承自 Emitter<emitterFactory, ServerStartedEvent>的類,在此我們將其稱為ServerStartedEventEmitter,用於通過HookRegistrar 註冊Hook。
  • 在該類的後面實現類型為std::unique_ptr<EmitterBase>的靜態函數,返回使用std::make_unique構造的 std::unique_ptr<ServerStartedEventEmitter>。
C++
#include "ll/api/event/server/ServerStartedEvent.h"
#include "ll/api/event/Emitter.h"
#include "ll/api/event/EventRefObjSerializer.h"
#include "ll/api/memory/Hook.h"

#include "mc/deps/nbt/CompoundTag.h"
#include "mc/scripting/ServerScriptManager.h"

namespace ll::event::inline server {

void ServerStartedEvent::serialize(CompoundTag& nbt) const {
    Event::serialize(nbt);
    nbt["server"] = serializeRefObj(server());
}

ServerInstance& ServerStartedEvent::server() const { return mInstance; }

LL_TYPE_INSTANCE_HOOK(
    ServerStartedEventHook,
    ll::memory::HookPriority::Normal,
    ServerScriptManager,
    &ServerScriptManager::$onServerThreadStarted,
    EventResult,
    ::ServerInstance& ins
) {
    auto result = origin(ins);
    EventBus::getInstance().publish(ServerStartedEvent(ins));
    return result;
}

static std::unique_ptr<EmitterBase> emitterFactory();
class ServerStartedEventEmitter : public Emitter<emitterFactory, ServerStartedEvent> {
    memory::HookRegistrar<ServerStartedEventHook> hook;
};

static std::unique_ptr<EmitterBase> emitterFactory() { return std::make_unique<ServerStartedEventEmitter>(); }
} // namespace ll::event::inline server