platform-velocity - Velocity プラグイン本体 (プロキシ中継)
platform-velocity はサーバー間グローバルチャットの中継だけを担う薄い層です.
実体は LunaticChat / BuildInfo / messaging/ の 2 ファイルのみで,コマンドクラスは持ちません./lcv は Velocity に関する機能ですが,コマンドの実装は platform-paper 側にある点に注意してください.
なぜ Velocity 側は薄いのか
protocol の定義は engine が持ち,チャットの状態 (チャンネル・DM・設定) はすべて Paper 側にあります.Velocity に残る責務は「受け取ったグローバルチャットを他サーバーへ配る」ことだけなので,この層は意図的に薄く保たれています.どちらのプラットフォームも protocol を所有せず,engine に対して対等に依存する構図です (詳細は 設計概要).
ライフサイクル
LunaticChat(@Plugin) — Guice の@InjectコンストラクタでProxyServer/Logger/PluginContainerを受け取る@Pluginアノテーションのversionは"0.0.0"固定で実行時には使われない.実バージョンはvelocity-plugin.jsonからPluginContainer.description.version経由で取得する (見つからなければ起動を止める)@Subscribe onProxyInitializationでCrossServerChatRelayを生成 → それを注入してPluginMessageHandlerを生成しinitialize()@Subscribe onProxyShutdownでmessageHandler.shutdown()
メッセージ受信とディスパッチ
PluginMessageHandler がチャンネル lunaticchat:main の受信を捌きます.initialize() で channelRegistrar.register(CHANNEL) とイベント購読を行います.
@Subscribe onPluginMessage の処理:
event.identifier != CHANNELなら無視する- 送信元が
ServerConnectionでなければ警告して破棄する PluginMessageCodec.decode()の結果をwhenで分岐するHandshake→ 互換判定してHandshakeResponse返送 /StatusRequest→StatusResponse返送 /GlobalChatMessage→ 中継へ委譲 / それ以外 (Velocity 発の応答型) → 警告のみ
信頼境界: クライアント由来メッセージの拒否
送信元が ServerConnection かどうかのチェックは,単なる型ガードではなく信頼境界です.
Velocity のプラグインメッセージはバックエンドサーバーだけでなくクライアントからも届き得ます.ここでバックエンド接続以外を弾くことで,クライアントがグローバルチャットや偽ハンドシェイクを直接注入することを防いでいます.中継されるのは信頼できるサーバー接続から来たメッセージだけです.
ハンドシェイク処理
Handshake を受け取ると engine の ProtocolVersion.isCompatible(major, minor) で互換性を判定します.
- 互換 —
compatible=trueのHandshakeResponseを返送 - 非互換 — Paper 側・Velocity 側のバージョンを載せた error 文字列とともに
compatible=falseを返送
HandshakeResponse / StatusResponse には Velocity 自身の ProtocolVersion (MAJOR / MINOR / PATCH) を必ず載せるため,Paper 側はレスポンスから相手のプロトコルを知ることができます.
サーバー間中継
CrossServerChatRelay.relayGlobalMessage(message, sourceServer) が中継の核心です.
server.allServers
.filter { it != sourceServer } // 送信元を除外
.forEach { it.sendPluginMessage(CHANNEL, encoded) }送信元サーバーを除外して残り全バックエンドへブロードキャストします (エコー防止の一段目) .中継件数はログに出ます.
中継されるもの / ローカルに留まるもの
中継の範囲を最小に絞っているのが設計上のポイントです.
- Velocity が他サーバーへ中継するのは
GlobalChatMessageのみ Handshake/HandshakeResponse/StatusRequest/StatusResponseは Velocity ↔ 単一 Paper の間で完結し,転送しない- DM・チャンネルチャットはそもそも Velocity へ送られない (Paper 内でローカル完結する)
エコー / ループ防止の二段構え
グローバルチャットが中継ループで多重表示されないよう,2 箇所で防いでいます.
- Velocity 側 — 送信元サーバーを除外してブロードキャスト
- Paper 側 —
messageIdによる重複排除 LRU キャッシュ (TTL 60s).送信側は生成直後に自分のmessageIdを登録して自サーバーでのエコーも防ぐ
通信フロー
- Paper がプレイヤー接続を契機に
Handshake(自プロトコルバージョン) を送信する - Velocity が
ProtocolVersion.isCompatibleで判定しHandshakeResponseを返送する → 互換なら Paper 側はCONNECTED - プレイヤーがグローバルチャット (チャンネル未所属 or
!プレフィックス) を送信 → Paper がGlobalChatMessage(新規messageId) を Velocity へ送信し,送信元では通常チャットを表示する - Velocity が送信元以外の全バックエンドへ中継する
- 各 Paper が受信 →
messageIdで dedup →crossServerGlobalChatFormatで整形して全プレイヤーへ配信する
実装ノート
PluginMessageHandlerのpluginパラメータ型がAnyなのは,Velocity のEventManager.register()がObjectを取るためです (API 自体が型安全でないので,ジェネリクス化しても実益が薄いとの判断).- クロスサーバーチャットを動かすには,Velocity の
velocity.tomlでbungee-plugin-message-channel=true(プラグインメッセージ有効) が必要です.
関連
- 設計概要
- engine - 共通カーネル — protocol の詳細
- platform-paper - Paper / Folia プラグイン本体 — Paper 側の対向実装