Suite

联动

Map 模块跨模块联动、公开 Capability、EventBus 主题与数据库表结构

联动

Capability 注册

Map 模块在 startService() 中注册以下 Capability,供其他模块跨模块调用:

MapNavigable(公开)

地图导航能力接口,标注 @PublicCapability + @Stable——AXS 模块内部经 getCapability(MapNavigable.class) 获取,外部 Bukkit 插件经 AxsCapabilities.get(MapNavigable.class) 获取(插件需 depend: [ArcartXSuite]softdepend)。

定位:地图是展示层与事件源。来源(QuestGPS / 外部插件)推送第三方插件注册路径点供玩家浏览;玩家点击"导航"时按优先级路由:全局委托 → useMapNavigation 地图代导航 → 来源 MapNavigationHandler 回调。

方法参数说明
upsertExternalTargetPlayer, MapExternalTargetSpec spec, boolean select新增/更新单个第三方插件注册路径点;select=true 切到目标世界并选中
upsertExternalTargetsPlayer, List<MapExternalTargetSpec>批量推送第三方插件注册路径点,一次视图同步
reconcileExternalTargetsPlayer, String source, List<MapExternalTargetSpec>用给定集合原子替换该来源全部目标:单次同步、仍存在的目标选中态不闪断;空列表 = 清空该来源
clearExternalTargetsPlayer, String source, boolean syncView按来源清除第三方插件注册路径点(source 为空串 = 全部来源)
setExternalNavigatedPlayer, String targetId, boolean navigated来源回推导航状态,驱动面板"导航 ↔ 取消"按钮(useMapNavigation 目标由地图自维护,无需调用)
navigateExternalTargetPlayer, String targetId由地图内置导航栈开始导航指定第三方插件注册路径点("地图代导航"命令式入口);已在导航返回 true,目标不存在/导航不可用返回 false
registerNavigationSourcePlugin owner, String source, MapNavigationHandler注册来源导航回调;owner 插件禁用时自动注销
unregisterNavigationSourceString source注销来源导航回调
setNavigationDelegatePlugin owner, MapNavigationDelegate注册全局导航委托(全量接管导航点击);全服仅一个,重复注册由后者覆盖;owner 插件禁用时自动注销
clearNavigationDelegatePlugin owner注销导航委托(仅当当前委托属于该插件时生效)
hasNavigationDelegate当前是否已注册全局导航委托
clearTrackPlayer静默清除地图自身导航——来源开始自身导航时调用,实现导航互斥
openMenuForPlayer, String targetId打开地图并选中目标;依次匹配第三方插件注册路径点 → 路径点 → 标记点,未命中按世界 ID 兜底打开

MapExternalTargetSpec

第三方插件注册路径点规格 record,字段:targetId / source / worldId / title / description / x / y / z / iconId / navigated / sortOrder / useMapNavigation(保留省略 useMapNavigation 的兼容构造,另有省略 iconId/navigated/sortOrder/useMapNavigation 的便捷构造)。

useMapNavigation=true 时,玩家点该目标的"导航"由地图内置导航栈执行(客户端路标 + 路径标记 + 到达判定),不回调来源 handler——来源推送后零代码获得导航能力;导航样式/到达半径/命令钩子走地图 navigation.* 配置。默认 false 走来源回调/全局委托。

MapNavigationHandler

public interface MapNavigationHandler {
    /** 玩家点击"导航"时回调;返回 true 表示已接管,false 提示"来源未提供导航支持" */
    boolean onNavigateRequest(Player player, String targetId);
 
    /** 玩家点击"取消"时回调;来源应停止自身导航,目标标记保留 */
    default void onCancelRequest(Player player, String targetId) {}
}

MapNavigationDelegate(全局委托)

setNavigationDelegate(owner, delegate) 注册的委托会先于一切导航处理拦截玩家在地图上的"导航"点击(锚点/路径点/第三方插件注册路径点;标记点 pin 不可导航不经此接口):

public interface MapNavigationDelegate {
    // true=已接管(地图记录委托态,面板按钮切"取消");false=交还原处理方
    boolean onNavigateRequest(Player player, MapNavigationRequest request);
    // 委托态被终止时回调(玩家取消/互斥清除/来源接管),实现需幂等
    default void onCancelRequest(Player player, MapNavigationRequest request) {}
}

MapNavigationRequest record 携带目标完整描述:targetTypeanchor/waypoint/external)、targetIdworldIdtitlex/y/zsource(自有目标固定 "map")。契约要点:

  • 导航执行(信标/路径标记/追踪状态)完全由委托方负责,地图不建客户端路标
  • 委托接管第三方插件注册路径点时应另调 setExternalNavigated(player, targetId, true) 驱动该目标行按钮切换
  • 全服同一时间只有一个委托;重复注册由后者覆盖;owner 插件禁用时自动注销

导航互斥约定

互斥体现在跨来源层面(来源 ↔ 地图自身的锚点/路径点导航不共存;来源内部多目标并行不受此约束):

  • 来源开始自身导航 → 调 clearTrack(player) 静默清除地图导航(nav_cleared,reason=source_took_over
  • 地图开始锚点/路径点导航 → 发布 axs.map.nav_started,来源应订阅并停止自身导航

调用示例(外部插件)

MapNavigable map = AxsCapabilities.get(MapNavigable.class);
if (map == null) return; // Map 模块未启用
 
// 注册来源回调(插件 disable 时自动注销)
map.registerNavigationSource(this, "myplugin", new MapNavigationHandler() {
    @Override public boolean onNavigateRequest(Player p, String targetId) {
        startMyNavigation(p, targetId);          // 自行创建信标/路径标记
        map.setExternalNavigated(p, targetId, true); // 回推状态 → 面板按钮变"取消"
        map.clearTrack(p);                        // 互斥:清掉地图自身导航
        return true;
    }
    @Override public void onCancelRequest(Player p, String targetId) {
        stopMyNavigation(p);
        map.setExternalNavigated(p, targetId, false);
    }
});
 
// 推送目标(批量推荐 reconcile,原子替换)
map.reconcileExternalTargets(player, "myplugin", List.of(
    new MapExternalTargetSpec("target-1", "myplugin", "world",
        "击杀世界Boss", "限时目标", 128.5, 64.0, -42.0, null, false, 0)
));
 
// 打开地图并选中目标
map.openMenuFor(player, "target-1");

upsertExternalTarget/reconcile/clearExternalTargets/openMenuFor 为同步主线程操作;registerNavigationSource/unregisterNavigationSource 线程安全。

PlayerDataPurgeable

玩家数据清除能力,支持 /axs purge 统一清理玩家地图数据。

方法参数说明
moduleId返回模块 ID map
purgePlayerDataUUID playerUuid清除指定玩家的全部地图数据(解锁记录 + 路径点 + 标记点),返回删除行数
purgeAllPlayerData清除所有玩家的全部地图数据,返回删除行数

purgePlayerData 在事务中删除 AXS_map_unlocksAXS_map_waypointsAXS_map_pins 三表中该玩家的所有记录。失败时返回 -1

DatabaseMigratable

数据库迁移能力,支持 /axs migrate map 跨源数据库迁移。

方法参数说明
moduleId返回模块 ID map
migrateDatabaseStorageDescriptor target, boolean overwrite将数据从当前存储迁移到目标存储
currentDescriptor返回当前存储描述符

迁移时支持从 SQLite 迁移到 MySQL 或反向迁移,overwritetrue 时覆盖目标库已有数据。

第三方插件对接方案

获取接口:AxsCapabilities.get(MapNavigable.class)(需 depend/softdepend: [ArcartXSuite] + compileOnly axs-api)。

第三方插件注册路径点"导航"点击按优先级路由:全局委托 → useMapNavigation 地图代导航 → 来源 handler → 提示「来源未提供导航支持」。按接入用途选择:

用途接入方式导航执行方
第三方插件注册路径点由 Map 执行导航spec 设 useMapNavigation=true,或调用 navigateExternalTargetMap 模块导航栈
第三方插件代理自身注册路径点导航registerNavigationSource 注册来源回调第三方插件
第三方插件代理全部地图标记点导航(含自带锚点/路径点)setNavigationDelegate第三方插件
仅展示第三方插件注册路径点,不提供导航仅推送第三方插件注册路径点

同一 source 可按目标混用:部分目标 useMapNavigation=true 交由 Map 导航,其余经来源 handler 自行执行。

第三方插件注册路径点由 Map 执行导航(useMapNavigation)

spec 设 useMapNavigation=true 后,点击"导航"由 Map 导航栈全程执行——客户端路标、路径标记、到达判定、finish-commands、按钮态翻转、取消、互斥、跨世界 cross_world_navigate

// 推送第三方插件注册路径点:useMapNavigation=true 表示该目标的导航由 Map 执行
map.reconcileExternalTargets(player, "myplugin", List.of(
    new MapExternalTargetSpec(
        "target-1", "myplugin", "world",
        "击杀世界Boss", "限时目标",
        128.5, 64.0, -42.0,
        null, false, 0, true)
));
 
// 等价命令式入口:不经面板点击,直接由 Map 开始导航该目标(不经全局委托)
map.navigateExternalTarget(player, "target-1");
  • 取消路径同样由 Map 处理:来源插件不接收 onCancelRequest,亦无需调用 setExternalNavigatednavigated 标志由 Map 维护)
  • upsert/reconcile 推送新坐标时自动刷新路标;目标被 reconcile/clear 移除时其导航态一并回收
  • 导航样式与行为参数(navigation.waypoint.*/finish-* 等)统一采用 Map 配置,来源插件不可按目标定制
  • navigateExternalTarget 亦可在来源 handler 内按需转交:同一 source 可混用"Map 执行"与"来源自执行"的目标

第三方插件代理自身注册路径点导航(来源回调)

来源插件注册 MapNavigationHandler:玩家点击"导航"/"取消"回调至来源插件,导航执行由来源插件完成:

MapNavigable map = AxsCapabilities.get(MapNavigable.class);
if (map == null) return; // Map 模块未安装或未启用
 
// 1. 注册来源回调:source 须与 spec.source 一致;owner 插件禁用时自动注销
map.registerNavigationSource(this, "myplugin", new MapNavigationHandler() {
 
    // 玩家点击"导航"时回调
    // 返回 true 表示已接管;返回 false 则提示「来源未提供导航支持」
    @Override
    public boolean onNavigateRequest(Player player, String targetId) {
        navService.start(player, targetId);
        // 回推导航状态:面板按钮由"导航"切换为"取消"
        map.setExternalNavigated(player, targetId, true);
        return true;
    }
 
    // 玩家点击"取消"时回调:停止导航并回推按钮状态(目标标记保留在地图上)
    @Override
    public void onCancelRequest(Player player, String targetId) {
        navService.stop(player, targetId);
        map.setExternalNavigated(player, targetId, false);
    }
});
 
// 2. 推送目标(原子替换该 source 的全部目标,建议批量 reconcile)
map.reconcileExternalTargets(player, "myplugin", specs);

互斥义务:来源插件开始导航前调用 map.clearTrack(player) 清除地图导航;订阅 axs.map.nav_started,在 Map 开始导航时停止自身导航。QuestGPS 模块即采用此模式。

第三方插件代理全部地图标记点导航(全局委托)

setNavigationDelegate(this, delegate) 后,锚点/路径点/第三方插件注册路径点的"导航"点击统一回调 onNavigateRequest(标记点 pin 不可导航,不经委托):

  • 返回 true 表示接管:Map 记录委托导航态并发布 nav_started不创建路标——路标/路径/到达判定均由委托方实现;返回 false 表示放行,移交下一级处理(可按 targetType/source 选择性接管)
  • onCancelRequest 在取消/互斥/玩家退出时触发,须保证幂等;取消逻辑应以 targetType + targetId 建立稳定映射——request 坐标为尽力重解(目标已删除时为 0),不可依赖坐标
  • 委托接管第三方插件注册路径点时需另调 setExternalNavigated 更新该行按钮;全局唯一委托,重复注册覆盖,owner 插件禁用时自动注销
  • 边界:cross_world_navigate 的传送段(含扣费)由 Map 先行执行,成功后移交委托方;传送/解锁/路径点管理不经委托;除 onCancelRequest 外无其他生命周期回调(无到达事件)
map.setNavigationDelegate(this, new MapNavigationDelegate() {
 
    // 点击任意"导航"先回调此方法:
    // 返回 true 接管(Map 记委托态、发 nav_started,但不建路标);
    // 返回 false 放行(第三方插件注册路径点 → useMapNavigation/来源 handler;自有目标 → Map 内置导航)
    @Override
    public boolean onNavigateRequest(Player player, MapNavigationRequest req) {
        // 示例策略:仅接管地图自有目标,第三方插件注册路径点放行给来源处理
        if (MapNavigationRequest.TYPE_EXTERNAL.equals(req.targetType())) {
            return false;
        }
        World world = Bukkit.getWorld(req.worldId());
        if (world == null) {
            return false;
        }
        // navId 必须建立稳定映射:取消回调时 request 坐标不可靠(目标已删除时为 0)
        String navId = "map-" + req.targetType() + "-" + req.targetId();
        navService.start(player, navId,
            new Location(world, req.x(), req.y(), req.z()), req.title());
        return true;
    }
 
    // 委托导航态被取消/互斥清除/玩家退出时回调;必须幂等
    @Override
    public void onCancelRequest(Player player, MapNavigationRequest req) {
        navService.stop(player, "map-" + req.targetType() + "-" + req.targetId());
    }
});
// 插件禁用时 Map 自动注销委托;也可显式调用 map.clearNavigationDelegate(this)

其他入口

  • 仅展示:仅推送目标、不注册 handler,点击"导航"提示「来源未提供导航支持」(适合位置公示,可在 description 注明)
  • 事件订阅:经 EventBusCapability 订阅下表主题——external_navigate 在回调/委托之前发布,各接管路径均可观测
  • 打开地图openMenuFor(player, targetId) 依次匹配第三方插件注册路径点 → 路径点 → 标记点,未命中按世界兜底打开

AXS 模块额外选项:复用导航栈

axs-apiNavigationTargetService / NavigationMarkerService / NavigationMarkerConfig / NavigationTarget 是可直接实例化的共享组件(Map 与 QuestGPS 的导航均构建其上)。AXS 模块经 createWaypointBridge() / createAdyeshachNpcBridge() 注入桥接后自行 new 实例,即可获得多目标并行登记 + 单路标单路线、diff 推送、到达滞回 + 命令钩子、NPC/动态 resolver、跨世界提示、标题模板等完整能力。

外部 Bukkit 插件拿不到 WaypointBridgeAPI/AdyeshachNpcBridgeAPI(非 @PublicCapability,仅注入 AXS 模块上下文),此路线仅适用于 AXS 模块。

EventBus 主题

Map 模块向 EventBus 发布以下主题,其他模块/外部插件可经 EventBusCapability 订阅:

主题触发时机Payload
axs.map.nav_started玩家开始导航(锚点/路径点,含委托接管与地图代导航的第三方插件注册路径点)type(anchor/waypoint/external)、idworld_id
axs.map.nav_cleared玩家停止地图导航(含委托态移除)type(anchor/waypoint/external)、idworld_idreasoncleared/来源接管 source_took_over
axs.map.external_navigate面板点击第三方插件注册路径点"导航"(在委托/来源回调之前发布,两种接管路径都能观测)target_idsourceworld_id
axs.map.external_nav_cancel面板点击第三方插件注册路径点"取消"target_idsourceworld_id

跨服同步

Map 模块的配置文件支持 SyncPolicyArcartXMap.yml 中以下段标记为动态段,可在跨服环境下同步配置:

同步段说明
worlds世界定义(动态段,跨服同步;仅内联兼容格式生效)
anchors锚点定义(动态段,跨服同步)
waypoints路径点配置(动态段,跨服同步)

其他配置段(uijoinstoragenavigationshare 等)为静态段,各服独立加载。注意:实际生效的世界/锚点定义位于 worlds/ 目录的独立文件中,不受 ArcartXMap.yml 节级同步覆盖——多服部署需自行保持各服 worlds/ 文件一致。

数据库表结构

Map 模块使用三张表存储玩家数据,表名前缀取决于存储模式。

共享模式表名

共享模式下使用本体统一表前缀,表名固定为 AXS_map_unlocksAXS_map_waypointsAXS_map_pins

自建模式表名

自建模式下使用 storage.mysql.table-prefix(默认 axs_map_)作为表名前缀。

表结构

AXS_map_unlocks(锚点解锁记录)

SQLite 类型MySQL 类型说明
uuidTEXT NOT NULLVARCHAR(36) NOT NULL玩家 UUID
anchor_idTEXT NOT NULLVARCHAR(128) NOT NULL锚点 ID
unlock_timeINTEGER NOT NULLBIGINT NOT NULL解锁时间戳(毫秒)
主键(uuid, anchor_id)(uuid, anchor_id)联合主键

AXS_map_waypoints(玩家路径点)

SQLite 类型MySQL 类型说明
uuidTEXT NOT NULLVARCHAR(36) NOT NULL玩家 UUID
waypoint_idTEXT NOT NULLVARCHAR(128) NOT NULL路径点 ID
nameTEXT NOT NULLVARCHAR(128) NOT NULL路径点名称
descriptionTEXT NOT NULLTEXT NOT NULL描述
worldTEXT NOT NULLVARCHAR(64) NOT NULL所在世界 ID
xREAL NOT NULLDOUBLE NOT NULLX 坐标
yREAL NOT NULLDOUBLE NOT NULLY 坐标
zREAL NOT NULLDOUBLE NOT NULLZ 坐标
created_atINTEGER NOT NULLBIGINT NOT NULL创建时间戳(毫秒)
updated_atINTEGER NOT NULLBIGINT NOT NULL更新时间戳(毫秒)
主键(uuid, waypoint_id)(uuid, waypoint_id)联合主键

AXS_map_pins(玩家标记点)

SQLite 类型MySQL 类型说明
uuidTEXT NOT NULLVARCHAR(36) NOT NULL玩家 UUID
pin_idTEXT NOT NULLVARCHAR(128) NOT NULL标记点 ID
nameTEXT NOT NULLVARCHAR(128) NOT NULL名称
descriptionTEXT NOT NULLTEXT NOT NULL描述
worldTEXT NOT NULLVARCHAR(64) NOT NULL所在世界 ID
xREAL NOT NULLDOUBLE NOT NULLX 坐标
zREAL NOT NULLDOUBLE NOT NULLZ 坐标
created_atINTEGER NOT NULLBIGINT NOT NULL创建时间戳(毫秒)
updated_atINTEGER NOT NULLBIGINT NOT NULL更新时间戳(毫秒)
主键(uuid, pin_id)(uuid, pin_id)联合主键

MySQL 表使用 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4。SQLite 使用 INSERT OR IGNORE / ON CONFLICT 语法,MySQL 使用 INSERT IGNORE / ON DUPLICATE KEY UPDATE 语法实现原子 upsert。

与其他模块联动

QuestGPS

联动方式说明
第三方插件注册路径点注入QuestGPS 通过 MapNavigable.reconcileExternalTargets 把进行中任务原子同步为地图第三方插件注册路径点(source = questgps),任务接取/失败/完成/进服时刷新
坐标跟随Chemdah tracker 动态坐标变化 > 0.5 格时经 upsertExternalTarget 更新目标坐标
导航回调QuestGPS 注册 MapNavigationHandler:面板"导航"→ trackQuest/trackTask,"取消"→ 停止追踪(标记保留)
导航互斥QuestGPS 起导航调 clearTrack;地图起导航发布 axs.map.nav_started,QuestGPS 订阅后停止自身任务导航
打开地图QuestGPS UI open_map 操作调用 MapNavigable.openMenuFor 打开 Map 菜单并选中目标

Map 模块不存在时 QuestGPS 导航功能正常运行,仅跳过地图标记同步。

Chat(点位分享)

联动方式说明
分享卡片share_waypoint / share_pin 通过 Chat 模块的 ChatCardSendable 发送 axs_map_share 卡片到聊天栏
卡片领取卡片按钮触发 claim_share packet 领取点位(不经 actionToken,分享码即凭据)
文本降级卡片不可用时降级为文本广播 + share-claim-button 点击文本

ArcartX Waypoint

联动方式说明
路标导航通过 WaypointBridgeAPI 创建/移除路标,在客户端显示方向指引
样式解析路标样式 ID 优先使用 navigation.waypoint.style-id,回退到 waypoints.default-style-id
初始化模块启动时调用 waypointBridge.initialize("Map 导航") 初始化路标系统

ArcartX Waypoint 不存在时锚点/路径点导航不可用(waypointRuntimeReady 返回 false),但地图菜单、HUD 与第三方插件注册路径点转发正常使用。

Adyeshach

联动方式说明
导航标记模型通过 AdyeshachNpcBridgeAPI 创建私有实体,使用 ArcartX 模型渲染客户端导航标记
路径标记沿导航路径放置标记模型,仅在追踪状态变更时发包,客户端独立渲染
Debug 模式debug: true 时 Adyeshach 桥接开启调试日志

Adyeshach 不存在时模型导航提示自动关闭(navigation.model-marker.enabled 仍可设为 true,但运行时检测不可用后跳过),路标导航与贴图导航提示(navigation.texture-marker)不受影响。

Multiverse-Core

联动方式说明
世界显示名世界文件 display-name 留空时,用 Multiverse colourless alias 自动补齐
小地图世界名map_hud.ymlPlaceholder.parse("%multiverse-core_alias%") 显示当前世界名

Vault / 货币系统

联动方式说明
锚点解锁费用通过 CurrencyBridgeAPI 扣除锚点解锁所需的货币
锚点传送费用通过 CurrencyBridgeAPI 扣除锚点传送所需的货币
余额检查解锁/传送前检查玩家货币余额是否充足
回滚机制扣费失败或后续操作失败时自动回滚已扣除的货币

Vault 不存在时锚点解锁/传送的货币消耗不可用。配置了货币消耗但桥接不可用时,操作失败并提示「货币桥接不可用」。

物品消耗

联动方式说明
锚点解锁物品通过 ItemMatcherAPI 匹配玩家背包物品,解锁时消耗指定数量
预检机制解锁前先检查背包是否有足够物品,不足时提示「缺少解锁所需物品」
回滚机制物品扣除后若后续操作失败,自动恢复原始背包内容

物品匹配器配置参考 /docs/guide/item-sources 中的物品匹配器语法。

ArcartX 核心

联动方式说明
UI 注册通过 registerModuleUi 注册地图菜单和小地图 HUD 两个 UI
Packet 通讯通过 PacketBridgeAPI 向客户端 UI 发送 init/update
Packet 安全通过 PacketGuardAPI 对客户端 Packet 进行权限校验(在路由层统一调用)
客户端初始化通过 ClientInitializedHandler 监听客户端初始化完成,触发自动解锁和 HUD 显示
按键绑定打开地图/切换小地图按键由宿主 config.ymlkeybinds 节统一注册
物品源通过 ItemSourceRegistry 获取物品源
物品匹配通过 ItemMatcherAPI 进行物品匹配
调度器通过 AxsScheduler 执行延迟任务(如入服 HUD 延迟显示)

配置版本迁移

Map 模块内置配置迁移脚本,自动将旧版本配置升级到当前版本:

迁移操作
1 → 2client 节的 UI 配置字段(menu-ui-id/hud-ui-id/packet-id/register-ui-on-enable/overwrite-ui-files)整体迁移到统一 ui 节,移除 client
2 → 3storage.mode 重命名为 storage.shared,值从 sqlite/mysql 映射为 true/falsestorage.shared 缺失时设为 true
3 → 4移除全局 default-unlocks 节,自动解锁改为 worlds/<world>.yml 锚点的 unlock.default / unlock.on-permission

迁移脚本位于 resources/migrations/ 目录,模块启动时自动检测 config-version 并按序执行迁移。