服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
149 lines
6.5 KiB
Go
149 lines
6.5 KiB
Go
// Package weaknesspotion owns the 防虚弱药剂 item family and the durable
|
|
// weakness-immunity window those items grant.
|
|
//
|
|
// Why the family is pinned as data instead of being derived from the PVF: the
|
|
// client applies the potion's buff locally and never reports it. A live capture
|
|
// (2026-09-15 00:43) shows the only uplink around a use is the ordinary
|
|
// class-1/op44 request - op593/op123/op42 carry no buff state - so the server has
|
|
// nothing to read. The PVF offers no structural tag either: [cooltime group] is
|
|
// the HP-potion group (雷米之吻), and the 71 variants (10/15/30/60-minute pots,
|
|
// 安魂香, 绿色/红色, 网吧/圣诞/兽人王国 versions ...) share no field except the
|
|
// [explain] text. The list below was enumerated from the live Script.pvf with
|
|
// zz_scan_daily_usable_count_test.go, so it is data, not guesswork.
|
|
//
|
|
// The window is durable on purpose: several variants state the effect survives a
|
|
// death, a town return and a relog ("该效果不会因角色在地下城中死亡、 返回城镇或
|
|
// 重新登录游戏而消失"), so it lives in the character's overflow stat map
|
|
// (dnf_character_stats) and needs no schema change.
|
|
package weaknesspotion
|
|
|
|
import "time"
|
|
|
|
const (
|
|
// ImmuneUntilStatKey is the unix second the current immunity window ends.
|
|
ImmuneUntilStatKey = "weakness_immune_until"
|
|
// WeaknessLevelStatKey is dnfbridge's penalty field: "stamina" carries the
|
|
// weakness level (currentWeaknessStatKey). Using a 防虚弱药剂 clears the
|
|
// penalty immediately as well as preventing the next one.
|
|
WeaknessLevelStatKey = "stamina"
|
|
// WeaknessRecoveryAtStatKey is dnfbridge's countdown anchor; it is cleared
|
|
// with the penalty so no half-finished recovery is left behind.
|
|
WeaknessRecoveryAtStatKey = "weakness_recovery_at"
|
|
)
|
|
|
|
// immunitySeconds maps every usable 防虚弱 item to its immunity duration. The
|
|
// duration is the item's own [explain] "效果持续N分钟"; the one variant whose
|
|
// text omits it uses its [cool time] (1800000 ms).
|
|
var immunitySeconds = map[int64]int64{
|
|
10005376: 1800, // 防虚弱药剂 (30 分钟)
|
|
10005909: 1800, // 防虚弱药剂 (30 分钟)
|
|
10006075: 1800, // 防虚弱药剂 (30 分钟)
|
|
10006487: 1800, // 防虚弱药剂 (30 分钟)
|
|
10006572: 900, // 防虚弱药剂 (15 分钟)
|
|
10087825: 1800, // 防虚弱药剂 (30 分钟)
|
|
490000028: 900, // 金蛋之王的祝福 (15 分钟)
|
|
490000113: 1800, // 防虚弱药剂 (30 分钟)
|
|
490000348: 1800, // 防虚弱药剂 (30 分钟)
|
|
490000702: 900, // 防虚弱药剂 (15 分钟)
|
|
490001415: 1800, // 防虚弱药剂 (30 分钟)
|
|
490002087: 1800, // 防虚弱药剂 (30 分钟)
|
|
490002486: 1800, // 解除虚弱状态药剂 (30 分钟)
|
|
490002492: 1800, // 防虚弱药剂 (30 分钟)
|
|
490003164: 1800, // 防虚弱药剂 (30 分钟)
|
|
490003223: 1800, // 防虚弱药剂 (30 分钟)
|
|
490003267: 1800, // 防虚弱药剂 (30 分钟)
|
|
490003594: 1800, // 防虚弱药剂 (30 分钟)
|
|
490003621: 600, // 防虚弱药剂 (10分钟) (10 分钟)
|
|
490003693: 1800, // 防虚弱药剂 (30 分钟)
|
|
490003699: 600, // 防虚弱药剂 (10 分钟)
|
|
490003991: 1800, // 防虚弱药剂 (30 分钟)
|
|
490004184: 1800, // 防虚弱药剂 (30 分钟)
|
|
490004191: 600, // 防虚弱药剂 (10 分钟)
|
|
490004314: 1800, // 防虚弱药剂 (30 分钟)
|
|
490004398: 1800, // 防虚弱药剂 (30 分钟)
|
|
490004403: 600, // 安魂香 (10 分钟)
|
|
490004641: 1800, // 防虚弱药剂 (30 分钟)
|
|
490005155: 1800, // 防虚弱药剂 (30 分钟)
|
|
490005202: 600, // 防虚弱药剂 (10 分钟)
|
|
490005299: 1800, // 防虚弱药剂 (30 分钟)
|
|
490005338: 600, // 防虚弱药剂 (10 分钟)
|
|
490005630: 600, // 防虚弱药剂 (10 分钟)
|
|
490005699: 1800, // 防虚弱药剂 (30 分钟)
|
|
490005756: 600, // 防虚弱药剂 (10 分钟)
|
|
490005942: 1800, // 防虚弱药剂 (30 分钟)
|
|
490006116: 1800, // 防虚弱药剂 (30 分钟)
|
|
490006128: 1800, // 防虚弱药剂 (30 分钟)
|
|
490006356: 600, // 圣诞老人的防虚弱药剂 (10 分钟)
|
|
490006403: 1800, // 防虚弱药剂 (30 分钟)
|
|
490006573: 1800, // 防虚弱药剂 (30 分钟)
|
|
490006758: 600, // 兽人王国的防虚弱药剂 (10 分钟)
|
|
490006790: 1800, // 防虚弱药剂 (30 分钟)
|
|
490006925: 600, // 防虚弱药剂 (10 分钟)
|
|
490006966: 1800, // 安徒恩的祝福 (30 分钟)
|
|
490007035: 600, // 防虚弱药剂 (10 分钟)
|
|
490007360: 600, // 防虚弱药剂 (10 分钟)
|
|
490007515: 600, // 绿色防虚弱药剂 (10分钟) (10 分钟)
|
|
490007894: 600, // 防虚弱药剂 (10 分钟)
|
|
490007908: 3600, // 史诗网吧防虚弱药剂 (60 分钟)
|
|
490007910: 3600, // 传说网吧防虚弱药剂 (60 分钟)
|
|
490007912: 1800, // 勇者网吧防虚弱药剂 (30 分钟)
|
|
490007926: 1800, // 防虚弱药剂 (30 分钟)
|
|
490008119: 600, // 防虚弱药剂 (10 分钟)
|
|
490008213: 900, // 防虚弱药剂(15分钟) (15 分钟)
|
|
490008358: 1800, // 防虚弱药剂 (30 分钟)
|
|
490008441: 600, // 防虚弱药剂 (10 分钟)
|
|
490008541: 600, // 防虚弱药剂 (枪剑士) (10 分钟)
|
|
490700369: 1800, // 防虚弱药剂 (30 分钟)
|
|
490700706: 1800, // 防虚弱药剂 (30 分钟)
|
|
490701082: 1800, // 防虚弱药剂 (30 分钟)
|
|
490701255: 1800, // 防虚弱药剂 (30 分钟)
|
|
490701313: 1800, // 防虚弱药剂 (30 分钟)
|
|
490701627: 600, // 红色防虚弱药剂 (10 分钟)
|
|
490701641: 1800, // 防虚弱药剂 (30 分钟)
|
|
490701705: 600, // 防虚弱药剂 (10 分钟)
|
|
490701808: 1800, // 防虚弱药剂 (30 分钟)
|
|
490702160: 1800, // 防虚弱药剂 (30 分钟)
|
|
490702200: 600, // 防虚弱药剂 (10 分钟)
|
|
490702311: 1800, // 防虚弱药剂 (30 分钟)
|
|
}
|
|
|
|
// heldItemIDs are the passive variants: merely carrying one in the material
|
|
// inventory already prevents the penalty, no use required.
|
|
var heldItemIDs = map[int64]struct{}{
|
|
490008656: {}, // 十周年防虚弱青石
|
|
}
|
|
|
|
// ImmunitySeconds reports the immunity window one item use grants.
|
|
func ImmunitySeconds(itemID int64) (int64, bool) {
|
|
seconds, ok := immunitySeconds[itemID]
|
|
if !ok || seconds <= 0 {
|
|
return 0, false
|
|
}
|
|
return seconds, true
|
|
}
|
|
|
|
// HeldExempts reports whether merely holding this item id already prevents the
|
|
// forced-return penalty.
|
|
func HeldExempts(itemID int64) bool {
|
|
_, ok := heldItemIDs[itemID]
|
|
return ok
|
|
}
|
|
|
|
// ImmuneUntil reads the stored window end (unix seconds); zero means none.
|
|
func ImmuneUntil(stats map[string]int64) int64 {
|
|
if stats == nil {
|
|
return 0
|
|
}
|
|
until := stats[ImmuneUntilStatKey]
|
|
if until < 0 {
|
|
return 0
|
|
}
|
|
return until
|
|
}
|
|
|
|
// Active reports whether the immunity window still covers now.
|
|
func Active(stats map[string]int64, now time.Time) bool {
|
|
until := ImmuneUntil(stats)
|
|
return until > 0 && until > now.Unix()
|
|
}
|