服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
package inventory
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
|
|
"longheng.io/server/internal/modules/dnf/alignedcmd"
|
|
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
|
)
|
|
|
|
// enchantRandomWeightBase is the er_enchant weight base: a candidate weight of
|
|
// 100000 is a certain outcome.
|
|
const enchantRandomWeightBase = 100000
|
|
|
|
// enchantRandomRestrictionAllows applies the [enchant random] er_slot /
|
|
// er_grade / er_seal target gates shared by the op50 reinforcement ticket
|
|
// scene and the op438 weapon-forging ticket family: er_slot 0 allows every
|
|
// part, 1 weapons, 2 armor, 3 accessories; an empty er_grade list allows every
|
|
// rarity; only er_seal == 1 demands a sealed target (the common er_seal 0/2
|
|
// carries no check).
|
|
func enchantRandomRestrictionAllows(ticket alignedcmd.EnchantRandomTicket, equipmentType string, rarity int64, target dnfrepo.ItemStack) bool {
|
|
switch ticket.SlotRestriction {
|
|
case 0:
|
|
case 1, 2, 3:
|
|
if enchantRandomSlotClass(equipmentType) != ticket.SlotRestriction {
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
if len(ticket.Rarities) > 0 {
|
|
allowed := false
|
|
for _, candidate := range ticket.Rarities {
|
|
if candidate == rarity {
|
|
allowed = true
|
|
break
|
|
}
|
|
}
|
|
if !allowed {
|
|
return false
|
|
}
|
|
}
|
|
if ticket.SealRestriction == 1 && firstExtraInt(target.Extra, 0, "seal_flag", "seal") == 0 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// enchantRandomSlotClass folds a PVF [equipment type] token into the er_slot
|
|
// classes: 1 weapon, 2 armor, 3 accessory, 0 anything else.
|
|
func enchantRandomSlotClass(equipmentType string) int64 {
|
|
switch strings.ToLower(strings.Trim(equipmentType, "` ")) {
|
|
case "[weapon]":
|
|
return 1
|
|
case "[coat]", "[shoulder]", "[pants]", "[shoes]", "[waist]":
|
|
return 2
|
|
case "[amulet]", "[wrist]", "[ring]":
|
|
return 3
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// selectEnchantRandomEntry rolls one er_enchant candidate. A single candidate
|
|
// keeps its own weight, so a one-entry ticket can still fail and retain the
|
|
// current level like a plain ticket; a weighted pick across several candidates
|
|
// always succeeds at the picked level.
|
|
func (o *Owner) selectEnchantRandomEntry(entries []alignedcmd.UpgradeTicketRandomEntry) (alignedcmd.UpgradeTicketRandomEntry, bool) {
|
|
if len(entries) == 0 {
|
|
return alignedcmd.UpgradeTicketRandomEntry{}, false
|
|
}
|
|
if len(entries) == 1 {
|
|
return entries[0], true
|
|
}
|
|
total := int64(0)
|
|
for _, entry := range entries {
|
|
if entry.Weight <= 0 {
|
|
continue
|
|
}
|
|
if total > math.MaxInt64-entry.Weight {
|
|
return alignedcmd.UpgradeTicketRandomEntry{}, false
|
|
}
|
|
total += entry.Weight
|
|
}
|
|
if total <= 0 {
|
|
return alignedcmd.UpgradeTicketRandomEntry{}, false
|
|
}
|
|
roll := o.enchantRandomIntn(total)
|
|
cursor := int64(0)
|
|
picked := alignedcmd.UpgradeTicketRandomEntry{}
|
|
for _, entry := range entries {
|
|
if entry.Weight <= 0 {
|
|
continue
|
|
}
|
|
picked = entry
|
|
cursor += entry.Weight
|
|
if roll < cursor {
|
|
break
|
|
}
|
|
}
|
|
return alignedcmd.UpgradeTicketRandomEntry{TargetLevel: picked.TargetLevel, Weight: enchantRandomWeightBase}, true
|
|
}
|
|
|
|
// enchantRandomIntn draws from the injected random source so ticket rolls stay
|
|
// deterministic under test, mirroring chronicleRandomIntn.
|
|
func (o *Owner) enchantRandomIntn(limit int64) int64 {
|
|
if limit <= 1 || o == nil || o.enchantRandom == nil {
|
|
return 0
|
|
}
|
|
bounded := limit
|
|
if bounded > math.MaxInt32 {
|
|
bounded = math.MaxInt32
|
|
}
|
|
value := int64(o.enchantRandom(int(bounded)))
|
|
if value < 0 {
|
|
return 0
|
|
}
|
|
if value >= limit {
|
|
return value % limit
|
|
}
|
|
return value
|
|
}
|