任务通知 (Quest Notifications)
基本信息
| 属性 | 值 |
|---|---|
| 实现类 | betterquesting.client.QuestNotification |
| 配置载体 | betterquesting.network.handlers.NoticeConfig |
| 通知条目 | QuestNotification.QuestNotice(内部类) |
| 渲染后端 | GTNHLib TitleAPI(com.gtnewhorizon.gtnhlib.client.title.TitleAPI) |
| 客户端配置项 | 16 项(见 客户端配置) |
| 逐任务覆写属性 | 14 项 betterquesting:notification_*(见 任务属性) |
| 触发封包 | betterquesting:notification(NetNotices) |
功能
任务完成 / 解锁 / 更新时,BetterQuesting 在屏幕中央弹出通知。通知队列是 private static final List<QuestNotice> notices = new ArrayList<>()(QuestNotification.java:46)—— 同一条通知可被多次入队,弹出时取 notices.get(0)(:238),显示时长到了就 notices.remove(0);队列清空且有待恢复界面时 mc.displayGuiScreen(pendingScreen)(:255-262),即通知会盖住当前界面、播完再还回去。
两种样式
effTitleMode(NoticeConfig) 决定走哪条渲染路径(QuestNotification.java:90-92):
return HAS_TITLE_API && "title".equals(effStyle(c));
title样式 + GTNHLib 在场 → 走TitleAPI大标题- 其余情况 → 走
QuestNotification自绘的小弹出通知 - GTNHLib 不在场时
title样式会被静默降级,因为HAS_TITLE_API为假
HAS_TITLE_API 的探测方式不是 Loader.isModLoaded,而是类加载探测(QuestNotification.java:39):检查资源 com/gtnewhorizon/gtnhlib/client/title/TitleAPI.class 是否存在。
覆写优先级(源码注释原文)
// ---- precedence resolution: quest value if set, else player; player "off" always wins ----(QuestNotification.java:84)
即:任务级覆写优先,其次玩家设置;而玩家的 "off" 永远压过一切。 实现为 effStyle(:86-88):
if ("off".equals(BQ_Settings.notificationStyle)) return "off";
return !"default".equals(c.style) ? c.style : BQ_Settings.notificationStyle;
其余量的回落规则统一为「任务值 >= 0 则用任务值,否则用玩家设置」:effDuration(:95)、effFadeIn(:99)、effFadeOut(:103)、TitleAPI.setIconScale(:134)、setEffectTier(:158)、setIconAnimation(:160-161)、setParticleEffect(:154-155)。
effShowIcon 是唯一用字符串三态的(:105-109):任务值 "yes" → 显示,"no" → 隐藏,其余(含 "default")→ 用玩家设置。
副标题的解析(resolveSubtitle,:112-119)有三层优先级:showHint 为真 → 固定提示 betterquesting.notice.customize_hint;任务自定义副标题非空 → 用它;否则用 notice.subTxt。
时间参数换算
秒 → tick 的换算在 showTitleIfAvailable(:121-131):
int fadeInTicks = Math.max(0, (int)(effFadeIn(c) * 20));
int fadeOutTicks = Math.max(0, (int)(effFadeOut(c) * 20));
int totalTicks = Math.max(fadeInTicks + fadeOutTicks + 1, (int)(effDuration(c) * 20));
int stayTicks = totalTicks - fadeInTicks - fadeOutTicks;
注意 totalTicks 的下限是 fadeIn + fadeOut + 1,保证 stayTicks >= 1 —— 即淡入 + 淡出之和超过显示时长时,总时长会被拉长到刚好容纳两者。
音效
播放发生在客户端队列出队时(QuestNotification.java:252-254):
float volume = notice.sound.equals(NativeProps.SOUND_COMPLETE.getDefault()) ? 0.25f : 1f;
mc.getSoundHandler().playSound(new QuestCompleteSound(new ResourceLocation(notice.sound), volume));
即:当通知音效等于 SOUND_COMPLETE 的默认值 "random.levelup" 时,音量被压到 0.25,其他音效用 1.0。任务可用 NativeProps.SOUND_COMPLETE 换成自己的音效来避开这个衰减。
图标动画:配置注释与实现不符
Notification Icon Animation 的配置注释只列出 3 个值(ConfigHandler.java:96):none, fly_in, spin。但 animStringToInt 的 switch 实际处理 12 个字符串(QuestNotification.java:173-202):
| 字符串 | 常量 | 配置注释是否提及 |
|---|---|---|
none(default 分支) |
ICON_ANIM_NONE |
✅ |
fly_in |
ICON_ANIM_FLY_IN |
✅ |
spin |
ICON_ANIM_SPIN |
✅ |
rise |
ICON_ANIM_RISE |
❌ |
slide |
ICON_ANIM_SLIDE |
❌ |
zoom |
ICON_ANIM_ZOOM |
❌ |
pop |
ICON_ANIM_POP |
❌ |
spin_reverse |
ICON_ANIM_SPIN_REVERSE |
❌ |
bounce |
ICON_ANIM_BOUNCE |
❌ |
wobble |
ICON_ANIM_WOBBLE |
❌ |
swing |
ICON_ANIM_SWING |
❌ |
slam |
ICON_ANIM_SLAM |
❌ |
tada |
ICON_ANIM_TADA |
❌ |
同理,粒子效果的 4 个取值(confetti / sparkle / firework / item_confetti,见 particleStringToInt,:204-215)与配置注释 none, confetti, sparkle, firework, item_confetti 一致(none 走 default 分支映射到 PARTICLE_NONE)。
⚠️ 结论:注释少列了 9 个动画值,但代码全部支持。 写配置时按注释填不会出错,但会错过 9 个可用动画。此处据实记录差异,不做「修正」。
数值
| 数值名 | 值 | 来源 |
|---|---|---|
| 客户端配置项数 | 16 | 客户端配置 |
| 逐任务覆写属性数 | 14 | NativeProps 的 notification_* 家族 |
| 代码支持的图标动画值 | 12 | animStringToInt(配置注释只列 3 个) |
| 代码支持的粒子效果值 | 5 | particleStringToInt:none + 4 种 |
Notification Effect Tier 范围 |
0 – 6 | ConfigHandler.java:122 |
| 秒 → tick 换算系数 | ×20 | QuestNotification.java:123-127 |
totalTicks 下限 |
fadeIn + fadeOut + 1 |
QuestNotification.java:126 |
SOUND_COMPLETE 默认音效的音量 |
0.25 | QuestNotification.java:252 |
| 其他音效的音量 | 1.0 | 同上 |
| 渲染后端缺失时的行为 | title 样式静默降级为小弹出 |
HAS_TITLE_API 门控(QuestNotification.java:39, 91) |