任务通知 (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)

相关条目