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,159 @@
|
||||
package pvp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
repo "longheng.io/server/internal/modules/dnf/repository"
|
||||
)
|
||||
|
||||
var ErrTotalRewardInventoryFull = errors.New("total mission reward inventory full")
|
||||
|
||||
type TotalRewardPlacement func(repo.InventoryRecord, int64, int64, time.Time) (repo.InventoryRecord, error)
|
||||
|
||||
type TotalRewards struct {
|
||||
Character repo.CharacterRecord
|
||||
Items map[int16]repo.ItemStack
|
||||
Granted, Pending []uint16
|
||||
}
|
||||
|
||||
func totalRewardKey(id uint16) string { return "pvp_total_target_" + strconv.Itoa(int(id)) + "_paid" }
|
||||
|
||||
// GrantTotalTargets consumes committed account progress. Progress survives an
|
||||
// interrupted delivery; the next result or login resumes it. Items, emblems and
|
||||
// the account-wide paid marker commit together, so changing characters or
|
||||
// replaying a result cannot pay a target twice. No speculative mail is created.
|
||||
func GrantTotalTargets(ctx context.Context, repos repo.Group, c *Catalog, accountID, selected string, now time.Time, place TotalRewardPlacement) (TotalRewards, error) {
|
||||
var result TotalRewards
|
||||
if c == nil || repos.RentalAssets == nil || now.IsZero() {
|
||||
return result, fmt.Errorf("total reward dependencies missing")
|
||||
}
|
||||
err := repo.WithinRentalAssetsScoped(ctx, repos.RentalAssets, accountID, selected,
|
||||
func(accounts repo.AccountRepository, characters repo.CharacterRepository, inventories repo.InventoryRepository, _ repo.EquipmentRepository) error {
|
||||
account, found, err := accounts.Load(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found || account.AccountID != accountID {
|
||||
return fmt.Errorf("total reward account missing")
|
||||
}
|
||||
character, found, err := characters.Load(ctx, selected)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found || character.AccountID != accountID || character.Stats["delete_flag"] != 0 {
|
||||
return fmt.Errorf("total reward character owner mismatch")
|
||||
}
|
||||
character = repo.CloneCharacter(character)
|
||||
if character.Stats == nil {
|
||||
character.Stats = map[string]int64{}
|
||||
}
|
||||
p, err := ReadTotalProgress(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := c.normalRows(totalMissionCharacter(accountID, p.Stats))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
complete := map[uint16]bool{}
|
||||
for _, row := range rows {
|
||||
complete[row.ID] = row.Progress == row.Target
|
||||
}
|
||||
rewardIDs := map[int64]bool{}
|
||||
var inventory repo.InventoryRecord
|
||||
loaded, dirty := false, false
|
||||
for _, m := range c.Normal {
|
||||
paid := p.Stats[totalRewardKey(m.ID)]
|
||||
if paid != 0 && paid != 1 || paid == 1 && !complete[m.ID] {
|
||||
return fmt.Errorf("invalid total reward marker %d", m.ID)
|
||||
}
|
||||
if m.RewardItemID > 0 {
|
||||
rewardIDs[m.RewardItemID] = true
|
||||
}
|
||||
if paid == 1 || !complete[m.ID] || m.PreRequired != 0 && !complete[m.PreRequired] {
|
||||
continue
|
||||
}
|
||||
balance := character.Stats[EmblemsKey]
|
||||
if m.Emblems < 0 || m.Emblems > math.MaxInt32 || balance < 0 || balance > math.MaxInt32-m.Emblems {
|
||||
return fmt.Errorf("total reward emblem overflow")
|
||||
}
|
||||
if m.RewardItemID > 0 {
|
||||
if place == nil {
|
||||
return fmt.Errorf("total mission item placement missing")
|
||||
}
|
||||
if !loaded {
|
||||
inventory, found, err = inventories.Load(ctx, selected)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found || inventory.CharacterID != selected {
|
||||
return fmt.Errorf("total reward inventory missing")
|
||||
}
|
||||
loaded = true
|
||||
}
|
||||
next, err := place(repo.CloneInventory(inventory), m.RewardItemID, m.RewardItemCount, now)
|
||||
if errors.Is(err, ErrTotalRewardInventoryFull) {
|
||||
result.Pending = append(result.Pending, m.ID)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if next.CharacterID != selected {
|
||||
return fmt.Errorf("total reward inventory owner changed")
|
||||
}
|
||||
inventory, dirty = next, true
|
||||
}
|
||||
character.Stats[EmblemsKey] = balance + m.Emblems
|
||||
p.Stats[totalRewardKey(m.ID)] = 1
|
||||
result.Granted = append(result.Granted, m.ID)
|
||||
}
|
||||
if len(result.Granted) > 0 {
|
||||
if dirty {
|
||||
if err := repo.SaveInventoryFields(ctx, inventories, inventory, repo.InventoryFieldSlots); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := repo.SaveCharacterFields(ctx, characters, character, repo.CharacterFieldStats); err != nil {
|
||||
return err
|
||||
}
|
||||
raw, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := repo.SaveAccountMetadataEntry(ctx, accounts, account, TotalProgressMetadataKey, string(raw), now); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Publish current absolute values also on an idempotent retry, including
|
||||
// an indeterminate commit that completed before the connection failed.
|
||||
if len(rewardIDs) > 0 && !loaded {
|
||||
inventory, _, err = inventories.Load(ctx, selected)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
result.Items = map[int16]repo.ItemStack{}
|
||||
for key, stack := range inventory.Slots {
|
||||
if !rewardIDs[stack.ItemID] {
|
||||
continue
|
||||
}
|
||||
var list, slot int
|
||||
if _, err := fmt.Sscanf(key, "%d:%d", &list, &slot); err == nil && list == 0 && slot >= 0 && slot <= math.MaxInt16 {
|
||||
result.Items[int16(slot)] = stack
|
||||
}
|
||||
}
|
||||
result.Character = character
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return TotalRewards{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in new issue
Block a user