DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,120 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/alignedcmd"
|
||||
repo "longheng.io/server/internal/modules/dnf/repository"
|
||||
)
|
||||
|
||||
// PlaceStackableReward stages a PVF-resolved reward without committing it. A
|
||||
// full inventory leaves the input untouched, including partially filled stacks.
|
||||
// The caller owns the transaction and its durable reward receipt.
|
||||
func PlaceStackableReward(record repo.InventoryRecord, item alignedcmd.MagicBoxRewardItem, count int64) (repo.InventoryRecord, error) {
|
||||
if count <= 0 || count > math.MaxInt32 || item.StackLimit < 0 || item.StackLimit > math.MaxInt32 || item.SlotEnd > 32766 {
|
||||
return repo.InventoryRecord{}, fmt.Errorf("invalid stackable reward contract")
|
||||
}
|
||||
// An omitted PVF stack limit means unlimited in the existing grant owner;
|
||||
// retain that behavior with an explicit bound for the native count field.
|
||||
if item.StackLimit == 0 {
|
||||
item.StackLimit = math.MaxInt32
|
||||
}
|
||||
staged := repo.CloneInventory(record)
|
||||
if staged.Slots == nil {
|
||||
staged.Slots = map[string]repo.ItemStack{}
|
||||
}
|
||||
if _, err := placeStackableRewardRows(staged.Slots, item, count); err != nil {
|
||||
return repo.InventoryRecord{}, err
|
||||
}
|
||||
return staged, nil
|
||||
}
|
||||
|
||||
// placeStackableRewardRows 按**行上限**放置可叠加奖励:先把范围内已有的同类行补满
|
||||
// (每行上限 = PVF [stack limit]),再用范围内的空格开新行,最后一行允许不满。
|
||||
//
|
||||
// 刻意不复用 owner.go 的 grantRandomRewardItem:本树那个版本把 [stack limit] 同时当作
|
||||
// "该道具在**整个容器**里的总量上限"(ContainerItemSpace),那是既有的、影响所有随机奖励
|
||||
// 路径的运营决策,不属于本次决斗场移植范围。决斗场总战绩奖励走参考实现的按行语义,
|
||||
// 因此这里自带一份放置逻辑,两者互不影响。
|
||||
func placeStackableRewardRows(items map[string]repo.ItemStack, reward alignedcmd.MagicBoxRewardItem, count int64) ([]int16, error) {
|
||||
if count <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if reward.ItemID <= 0 || reward.Kind != "stackable" || reward.TargetListType != listTypeMain || reward.SlotStart < 0 || reward.SlotEnd < reward.SlotStart {
|
||||
return nil, fmt.Errorf("%w: item=%d kind=%q target_list=%d slots=%d..%d", ErrRandomRewardContractRequired, reward.ItemID, reward.Kind, reward.TargetListType, reward.SlotStart, reward.SlotEnd)
|
||||
}
|
||||
remaining := count
|
||||
changed := make([]int16, 0, 2)
|
||||
markChanged := func(slot int16) {
|
||||
for _, previous := range changed {
|
||||
if previous == slot {
|
||||
return
|
||||
}
|
||||
}
|
||||
changed = append(changed, slot)
|
||||
}
|
||||
// 第一遍:补进范围内已有的同类行。
|
||||
for slot := reward.SlotStart; slot <= reward.SlotEnd && remaining > 0; slot++ {
|
||||
key := slotKey(listTypeMain, slot)
|
||||
stack, found := items[key]
|
||||
if !found || stack.ItemID != reward.ItemID || stack.Count <= 0 || reward.StackLimit == 1 || !randomRewardExpirationMatches(stack, reward.ExpireAt) {
|
||||
continue
|
||||
}
|
||||
capacity := remaining
|
||||
if reward.StackLimit > 0 {
|
||||
capacity = reward.StackLimit - stack.Count
|
||||
}
|
||||
if capacity <= 0 {
|
||||
continue
|
||||
}
|
||||
add := remaining
|
||||
if add > capacity {
|
||||
add = capacity
|
||||
}
|
||||
stack = cloneStack(stack)
|
||||
stack.Count += add
|
||||
updateStackRawAmount(&stack)
|
||||
items[key] = stack
|
||||
remaining -= add
|
||||
markChanged(slot)
|
||||
}
|
||||
// 第二遍:占用范围内的空格开新行,每行最多 StackLimit。
|
||||
for slot := reward.SlotStart; slot <= reward.SlotEnd && remaining > 0; slot++ {
|
||||
key := slotKey(listTypeMain, slot)
|
||||
if _, occupied := items[key]; occupied {
|
||||
continue
|
||||
}
|
||||
insert := remaining
|
||||
if reward.StackLimit > 0 && insert > reward.StackLimit {
|
||||
insert = reward.StackLimit
|
||||
}
|
||||
stack := repo.ItemStack{
|
||||
ItemID: reward.ItemID,
|
||||
Count: insert,
|
||||
ExpireAt: reward.ExpireAt,
|
||||
Extra: map[string]string{
|
||||
"item_kind": "stackable",
|
||||
"pvf_path": reward.PVFPath,
|
||||
},
|
||||
}
|
||||
if !reward.ExpireAt.IsZero() && reward.ExpireAt.Unix() > 0 {
|
||||
expire := strconv.FormatInt(reward.ExpireAt.Unix(), 10)
|
||||
stack.Extra["expire_time"] = expire
|
||||
stack.Extra["expire_unix"] = expire
|
||||
}
|
||||
if reward.UsablePeriodDays > 0 {
|
||||
stack.Extra["usable_period_days"] = strconv.FormatInt(reward.UsablePeriodDays, 10)
|
||||
stack.Extra["expiration_source"] = "runtime_pvf_usable_period_grant"
|
||||
}
|
||||
updateStackRawAmount(&stack)
|
||||
items[key] = stack
|
||||
remaining -= insert
|
||||
markChanged(slot)
|
||||
}
|
||||
if remaining > 0 {
|
||||
return nil, fmt.Errorf("%w: item=%d remaining=%d", ErrRandomRewardInventoryFull, reward.ItemID, remaining)
|
||||
}
|
||||
return changed, nil
|
||||
}
|
||||
Reference in new issue
Block a user