消息服务 LinMessenger 用于向玩家或控制台投递一次性的聊天、动作栏与标题文本。它负责连接语言引用、高级字符串、全局前缀与 Bukkit 发送接口。
通过 Linlang Facade 取得消息服务:
LinMessenger messenger = lin.linMessenger();
直接发送普通文本:
messenger.send(player, "操作成功");
直接发送语言字段引用:
messenger.send(player, lang.command.success);
带有命名变量时,将变量名称和值交替传入:
messenger.send(
player,
lang.command.success,
"player", player.getName(),
"item", itemName
);
参数必须按「名称、值」成对出现,参数数量为奇数时会抛出 IllegalArgumentException。已经组织好的变量也可以通过 Map<String, ?> 重载传入。
字符串参数会作为普通文本安全插入。LinText、LangText 等 TextSource 参数会作为独立的高级文本片段插入,具体规则请见:高级字符串 ⇱。
聊天消息默认使用当前 Facade 的全局前缀。
发送动作栏:
messenger.actionBar(
player,
lang.status,
"progress", progress
);
使用默认时长发送标题:
messenger.title(player, lang.title, lang.subtitle);
需要指定淡入、停留和淡出时长时,使用 TitleTimes。时长单位为游戏刻:
messenger.title(
player,
lang.title,
lang.subtitle,
TitleTimes.of(10, 60, 20)
);
动作栏与标题默认不使用全局前缀。接收者不支持动作栏或标题时,新接口默认报告错误,不会自动改发聊天消息。
通常直接调用发送方法即可。需要进一步设置前缀、降级策略或标题时长时,可以手动创建 LinMessage:
messenger.send(
sender,
LinMessage.actionBar(lang.status)
.args("progress", progress)
.fallback(FallbackPolicy.CHAT)
);
前缀策略 PrefixMode 包含:
AUTO:聊天使用前缀,动作栏和标题不使用。INCLUDE:明确使用前缀。OMIT:明确省略前缀。下面的消息即使通过聊天发送,也不会带有前缀:
messenger.send(
player,
LinMessage.chat(lang.message).withoutPrefix()
);
通道不受支持时可以选择:
REJECT:报告调用错误,是新消息的默认值。CHAT:明确降级为聊天消息。IGNORE:忽略本次投递。LinMessage 是不可变对象。调用 args()、prefix()、fallback() 或 times() 会返回新的消息对象,因此预先保存的消息可以安全复用。
将 LangText 直接传给 Messenger,发送时会读取当前文本,不必先转换为 String:
LangText message = lang.command.success;
messenger.send(player, message);
langService.reload();
messenger.send(player, message);
第二次发送会使用重载后的语言文本。lin.settings().apply() 切换语言时同样保留引用,不需要重新绑定语言对象。
sendKey()、sendText()、sendTitleKey() 与 sendActionBarKey() 等旧方法仍然保留,以便已有插件逐步迁移,但已经标记为弃用。新代码不应再把语言路径字符串交给 Messenger,应直接使用 LangText 字段。
Messenger 不直接限定消息只能发送到当前 Bukkit 服务器。实际投递由 MessageTransport 完成,Bukkit Runtime 默认注册了本地传输层。
传输层接口包含四项职责:
TransportMessage。未来网络模块可以定义远程接收者并注册对应传输层:
public final class NetworkMessageTransport implements MessageTransport {
@Override
public String id() {
return "linlang-network";
}
@Override
public int priority() {
return 100;
}
@Override
public boolean supports(Object recipient) {
return recipient instanceof RemoteAudience;
}
@Override
public void send(Object recipient, TransportMessage message) {
RemoteAudience remote = (RemoteAudience) recipient;
network.send(remote.server(), remote.playerId(), message);
}
}
注册传输层:
lin.linMessenger().registerTransport(new NetworkMessageTransport());
相同标识的新传输层会替换旧实现。多个传输层同时支持同一接收者时,优先级较高的实现先接收消息;没有传输层支持接收者时,Messenger 会抛出 IllegalArgumentException。
普通重载、语言切换、前缀更新和 settings().apply() 均保留当前 Messenger,已注册的传输层可以继续使用。显式重建或重新初始化 Facade 后,需要为新消息服务注册传输层。
TransportMessage 中的语言引用、变量和前缀已经在发送端解析,内容只包含固定的 LinText 源码、消息通道、标题时长与降级策略,不包含 Bukkit 类型。网络模块可以据此建立稳定的序列化协议,接收端再使用对应平台的高级字符串渲染器完成显示。
flowchart LR
Call["send、actionBar 或 title"] --> Model["建立内部 LinMessage"]
Model --> Resolve["解析 TextSource"]
Resolve --> Args["绑定并转义变量"]
Args --> Prefix["应用前缀策略"]
Prefix --> Route["选择 MessageTransport"]
Route --> Local["Bukkit 本地投递"]
Route --> Network["未来网络投递"]
BossBar、声音、粒子和 GUI 具有不同生命周期,不属于一次性文本消息,不会并入 LinMessenger。GUI 名称与 Lore 可以复用高级字符串解析能力,但由 LinView 自己负责渲染和更新。
讨论